aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build.sbt12
-rw-r--r--kamon-core-bench/src/main/scala/kamon/bench/TagSetCreationBenchmark.scala42
-rw-r--r--kamon-core-bench/src/main/scala/kamon/bench/TagSetLookupBenchmark.scala55
-rw-r--r--kamon-core-tests/src/test/scala/kamon/context/BinaryPropagationSpec.scala12
-rw-r--r--kamon-core-tests/src/test/scala/kamon/context/HttpPropagationSpec.scala21
-rw-r--r--kamon-core-tests/src/test/scala/kamon/instrumentation/HttpServerInstrumentationSpec.scala13
-rw-r--r--kamon-core-tests/src/test/scala/kamon/tag/TagSetSpec.scala180
-rw-r--r--kamon-core/src/main/colfer/Context.colf27
-rw-r--r--kamon-core/src/main/java/kamon/context/generated/binary/context/BooleanTag.java412
-rw-r--r--kamon-core/src/main/java/kamon/context/generated/binary/context/Context.java107
-rw-r--r--kamon-core/src/main/java/kamon/context/generated/binary/context/Entry.java76
-rw-r--r--kamon-core/src/main/java/kamon/context/generated/binary/context/LongTag.java443
-rw-r--r--kamon-core/src/main/java/kamon/context/generated/binary/context/StringTag.java466
-rw-r--r--kamon-core/src/main/java/kamon/context/generated/binary/context/Tags.java513
-rw-r--r--kamon-core/src/main/scala/kamon/context/BinaryPropagation.scala81
-rw-r--r--kamon-core/src/main/scala/kamon/context/Context.scala126
-rw-r--r--kamon-core/src/main/scala/kamon/context/HttpPropagation.scala65
-rw-r--r--kamon-core/src/main/scala/kamon/instrumentation/HttpServer.scala24
-rw-r--r--kamon-core/src/main/scala/kamon/metric/Accumulator.scala6
-rw-r--r--kamon-core/src/main/scala/kamon/metric/Metric.scala24
-rw-r--r--kamon-core/src/main/scala/kamon/metric/PeriodSnapshot.scala4
-rw-r--r--kamon-core/src/main/scala/kamon/metric/Timer.scala6
-rw-r--r--kamon-core/src/main/scala/kamon/package.scala2
-rw-r--r--kamon-core/src/main/scala/kamon/tag/Lookups.scala159
-rw-r--r--kamon-core/src/main/scala/kamon/tag/Tag.scala50
-rw-r--r--kamon-core/src/main/scala/kamon/tag/TagSet.scala427
-rw-r--r--kamon-status-page/src/main/vue/dist/favicon/android-chrome-96x96.png~HEAD (renamed from kamon-status-page/src/main/vue/dist/favicon/android-chrome-96x96.png)bin5296 -> 5296 bytes
-rw-r--r--kamon-status-page/src/main/vue/dist/favicon/android-chrome-96x96.png~separate the status-page project from the status APIbin0 -> 5296 bytes
-rw-r--r--kamon-status-page/src/main/vue/dist/favicon/apple-touch-icon.png~HEAD (renamed from kamon-status-page/src/main/vue/dist/favicon/apple-touch-icon.png)bin2422 -> 2422 bytes
-rw-r--r--kamon-status-page/src/main/vue/dist/favicon/apple-touch-icon.png~separate the status-page project from the status APIbin0 -> 2422 bytes
-rw-r--r--kamon-status-page/src/main/vue/dist/favicon/browserconfig.xml~HEAD (renamed from kamon-status-page/src/main/vue/dist/favicon/browserconfig.xml)0
-rw-r--r--kamon-status-page/src/main/vue/dist/favicon/browserconfig.xml~separate the status-page project from the status API9
-rw-r--r--kamon-status-page/src/main/vue/dist/favicon/favicon-16x16.png~HEAD (renamed from kamon-status-page/src/main/vue/dist/favicon/favicon-16x16.png)bin1151 -> 1151 bytes
-rw-r--r--kamon-status-page/src/main/vue/dist/favicon/favicon-16x16.png~separate the status-page project from the status APIbin0 -> 1151 bytes
-rw-r--r--kamon-status-page/src/main/vue/dist/favicon/favicon-32x32.png~HEAD (renamed from kamon-status-page/src/main/vue/dist/favicon/favicon-32x32.png)bin1556 -> 1556 bytes
-rw-r--r--kamon-status-page/src/main/vue/dist/favicon/favicon-32x32.png~separate the status-page project from the status APIbin0 -> 1556 bytes
-rw-r--r--kamon-status-page/src/main/vue/dist/favicon/favicon.ico~HEAD (renamed from kamon-status-page/src/main/vue/dist/favicon/favicon.ico)bin15086 -> 15086 bytes
-rw-r--r--kamon-status-page/src/main/vue/dist/favicon/favicon.ico~separate the status-page project from the status APIbin0 -> 15086 bytes
-rw-r--r--kamon-status-page/src/main/vue/dist/favicon/mstile-150x150.png~HEAD (renamed from kamon-status-page/src/main/vue/dist/favicon/mstile-150x150.png)bin4918 -> 4918 bytes
-rw-r--r--kamon-status-page/src/main/vue/dist/favicon/mstile-150x150.png~separate the status-page project from the status APIbin0 -> 4918 bytes
-rw-r--r--kamon-status-page/src/main/vue/dist/favicon/safari-pinned-tab.svg~HEAD (renamed from kamon-status-page/src/main/vue/dist/favicon/safari-pinned-tab.svg)0
-rw-r--r--kamon-status-page/src/main/vue/dist/favicon/safari-pinned-tab.svg~separate the status-page project from the status API23
-rw-r--r--kamon-status-page/src/main/vue/dist/favicon/site.webmanifest~HEAD (renamed from kamon-status-page/src/main/vue/dist/favicon/site.webmanifest)0
-rw-r--r--kamon-status-page/src/main/vue/dist/favicon/site.webmanifest~separate the status-page project from the status API14
-rw-r--r--kamon-status-page/src/main/vue/dist/img/logo.592500c9.svg~HEAD (renamed from kamon-status-page/src/main/vue/dist/img/logo.592500c9.svg)0
-rw-r--r--kamon-status-page/src/main/vue/dist/img/logo.592500c9.svg~separate the status-page project from the status API26
46 files changed, 3164 insertions, 261 deletions
diff --git a/build.sbt b/build.sbt
index 5f62b887..538b3804 100644
--- a/build.sbt
+++ b/build.sbt
@@ -47,6 +47,7 @@ val commonSettings = Seq(
ShadeRule.rename("com.grack.nanojson.**" -> "kamon.lib.@0").inAll,
ShadeRule.rename("org.jctools.**" -> "kamon.lib.@0").inAll,
ShadeRule.rename("fi.iki.elonen.**" -> "kamon.lib.@0").inAll,
+ ShadeRule.rename("org.eclipse.**" -> "kamon.lib.@0").inAll,
),
assemblyExcludedJars in assembly := {
val cp = (fullClasspath in assembly).value
@@ -56,7 +57,7 @@ val commonSettings = Seq(
packageBin in Compile := assembly.value,
assemblyJarName in assembly := s"${moduleName.value}_${scalaBinaryVersion.value}-${version.value}.jar",
pomPostProcess := { originalPom => {
- val shadedGroups = Seq("org.hdrhistogram", "org.jctools", "org.nanohttpd", "com.grack")
+ val shadedGroups = Seq("org.hdrhistogram", "org.jctools", "org.nanohttpd", "com.grack", "org.eclipse.collections")
val filterShadedDependencies = new RuleTransformer(new RewriteRule {
override def transform(n: Node): Seq[Node] = {
if(n.label == "dependency") {
@@ -79,10 +80,11 @@ lazy val core = (project in file("kamon-core"))
buildInfoKeys := Seq[BuildInfoKey](version),
buildInfoPackage := "kamon.status",
libraryDependencies ++= Seq(
- "com.typesafe" % "config" % "1.3.1",
- "org.hdrhistogram" % "HdrHistogram" % "2.1.9",
- "org.jctools" % "jctools-core" % "2.1.1",
- "org.slf4j" % "slf4j-api" % "1.7.25"
+ "com.typesafe" % "config" % "1.3.1",
+ "org.hdrhistogram" % "HdrHistogram" % "2.1.9",
+ "org.jctools" % "jctools-core" % "2.1.1",
+ "org.eclipse.collections" % "eclipse-collections" % "9.2.0",
+ "org.slf4j" % "slf4j-api" % "1.7.25"
)
)
diff --git a/kamon-core-bench/src/main/scala/kamon/bench/TagSetCreationBenchmark.scala b/kamon-core-bench/src/main/scala/kamon/bench/TagSetCreationBenchmark.scala
new file mode 100644
index 00000000..9b8f1d7a
--- /dev/null
+++ b/kamon-core-bench/src/main/scala/kamon/bench/TagSetCreationBenchmark.scala
@@ -0,0 +1,42 @@
+package kamon.bench
+
+import java.util.concurrent.TimeUnit
+
+import kamon.tag.TagSet
+import org.openjdk.jmh.annotations._
+
+@BenchmarkMode(Array(Mode.AverageTime))
+@OutputTimeUnit(TimeUnit.NANOSECONDS)
+@Fork(1)
+@State(Scope.Benchmark)
+class TagSetCreationBenchmark {
+
+ @Param(Array("1", "2", "3", "4", "5", "6"))
+ var tagCount: Int = 1
+
+ @Benchmark
+ def createTagSetFromIndividualKeys(): TagSet = {
+ var tags = TagSet.Empty
+ tags = tags.withTag("http.method", "POST")
+ if(tagCount > 1) tags = tags.withTag("http.url", "http://localhost:8080/test")
+ if(tagCount > 2) tags = tags.withTag("http.status_code", 200L)
+ if(tagCount > 3) tags = tags.withTag("error", false)
+ if(tagCount > 4) tags = tags.withTag("userID", "abcdef")
+ if(tagCount > 5) tags = tags.withTag("correlationID", "0123456")
+
+ tags
+ }
+
+ @Benchmark
+ def createTagSetFromBuilder(): TagSet = {
+ val tags = TagSet.builder()
+ tags.add("http.method", "POST")
+ if(tagCount > 1) tags.add("http.url", "http://localhost:8080/test")
+ if(tagCount > 2) tags.add("http.status_code", 200L)
+ if(tagCount > 3) tags.add("error", false)
+ if(tagCount > 4) tags.add("userID", "abcdef")
+ if(tagCount > 5) tags.add("correlationID", "0123456")
+
+ tags.create()
+ }
+}
diff --git a/kamon-core-bench/src/main/scala/kamon/bench/TagSetLookupBenchmark.scala b/kamon-core-bench/src/main/scala/kamon/bench/TagSetLookupBenchmark.scala
new file mode 100644
index 00000000..b8a63d84
--- /dev/null
+++ b/kamon-core-bench/src/main/scala/kamon/bench/TagSetLookupBenchmark.scala
@@ -0,0 +1,55 @@
+package kamon.bench
+
+import java.util.concurrent.TimeUnit
+
+import kamon.tag.TagSet
+import org.openjdk.jmh.annotations._
+import kamon.tag.Lookups.any
+
+@BenchmarkMode(Array(Mode.AverageTime))
+@OutputTimeUnit(TimeUnit.NANOSECONDS)
+@Fork(1)
+@State(Scope.Benchmark)
+class TagSetLookupBenchmark {
+
+ def builderTags() = TagSet.builder()
+ .add("http.url", "http://localhost:8080/test")
+ .add("http.status_code", 200L)
+ .add("error", false)
+ .add("userID", "abcdef")
+ .add("correlationID", "0123456")
+ .create()
+
+ def keyByKeyTags() = TagSet.Empty
+ .withTag("http.url", "http://localhost:8080/test")
+ .withTag("http.status_code", 200L)
+ .withTag("error", false)
+ .withTag("userID", "abcdef")
+ .withTag("correlationID", "0123456")
+
+
+ val builderLeft = builderTags()
+ val builderRight = builderTags()
+ val keyByKeyLeft = keyByKeyTags()
+ val keyByKeyRight = keyByKeyTags()
+
+ @Benchmark
+ def equalityOnBuilderTagSets(): Boolean = {
+ builderLeft == builderRight
+ }
+
+ @Benchmark
+ def equalityOnKeyByKeyTagSets(): Boolean = {
+ keyByKeyLeft == keyByKeyRight
+ }
+
+ @Benchmark
+ def anyLookupOnBuilderTagSet(): Any = {
+ builderLeft.get(any("userID"))
+ }
+
+ @Benchmark
+ def anyLookupOnKeyByKeyTagSet(): Any = {
+ keyByKeyLeft.get(any("userID"))
+ }
+}
diff --git a/kamon-core-tests/src/test/scala/kamon/context/BinaryPropagationSpec.scala b/kamon-core-tests/src/test/scala/kamon/context/BinaryPropagationSpec.scala
index 5681d300..4fa7116d 100644
--- a/kamon-core-tests/src/test/scala/kamon/context/BinaryPropagationSpec.scala
+++ b/kamon-core-tests/src/test/scala/kamon/context/BinaryPropagationSpec.scala
@@ -6,6 +6,8 @@ import com.typesafe.config.ConfigFactory
import kamon.Kamon
import kamon.context.BinaryPropagation.{ByteStreamReader, ByteStreamWriter}
import kamon.context.Propagation.{EntryReader, EntryWriter}
+import kamon.tag.TagSet
+import kamon.tag.Lookups._
import org.scalatest.{Matchers, OptionValues, WordSpec}
import scala.util.Random
@@ -70,13 +72,14 @@ class BinaryPropagationSpec extends WordSpec with Matchers with OptionValues {
}
"round trip a Context that only has tags" in {
- val context = Context.of(Map("hello" -> "world", "kamon" -> "rulez"))
+ val context = Context.of(TagSet.from(Map("hello" -> "world", "kamon" -> "rulez")))
val writer = inspectableByteStreamWriter()
binaryPropagation.write(context, writer)
val rtContext = binaryPropagation.read(ByteStreamReader.of(writer.toByteArray))
rtContext.entries shouldBe empty
- rtContext.tags should contain theSameElementsAs (context.tags)
+ rtContext.tags.get(plain("hello")) shouldBe "world"
+ rtContext.tags.get(plain("kamon")) shouldBe "rulez"
}
"round trip a Context that only has entries" in {
@@ -91,7 +94,7 @@ class BinaryPropagationSpec extends WordSpec with Matchers with OptionValues {
}
"round trip a Context that with tags and entries" in {
- val context = Context.of(Map("hello" -> "world", "kamon" -> "rulez"))
+ val context = Context.of(TagSet.from(Map("hello" -> "world", "kamon" -> "rulez")))
.withKey(BinaryPropagationSpec.StringKey, "string-value")
.withKey(BinaryPropagationSpec.IntegerKey, 42)
@@ -99,7 +102,8 @@ class BinaryPropagationSpec extends WordSpec with Matchers with OptionValues {
binaryPropagation.write(context, writer)
val rtContext = binaryPropagation.read(ByteStreamReader.of(writer.toByteArray))
- rtContext.tags should contain theSameElementsAs (context.tags)
+ rtContext.tags.get(plain("hello")) shouldBe "world"
+ rtContext.tags.get(plain("kamon")) shouldBe "rulez"
rtContext.get(BinaryPropagationSpec.StringKey) shouldBe "string-value"
rtContext.get(BinaryPropagationSpec.IntegerKey) shouldBe 0 // there is no entry configuration for the integer key
}
diff --git a/kamon-core-tests/src/test/scala/kamon/context/HttpPropagationSpec.scala b/kamon-core-tests/src/test/scala/kamon/context/HttpPropagationSpec.scala
index fcddfe24..0cd10672 100644
--- a/kamon-core-tests/src/test/scala/kamon/context/HttpPropagationSpec.scala
+++ b/kamon-core-tests/src/test/scala/kamon/context/HttpPropagationSpec.scala
@@ -5,6 +5,8 @@ import kamon.Kamon
import kamon.context.HttpPropagation.{HeaderReader, HeaderWriter}
import kamon.context.Propagation.{EntryReader, EntryWriter}
import org.scalatest.{Matchers, OptionValues, WordSpec}
+import kamon.tag.Lookups._
+import kamon.tag.TagSet
import scala.collection.mutable
@@ -22,12 +24,11 @@ class HttpPropagationSpec extends WordSpec with Matchers with OptionValues {
"x-content-tags" -> "hello=world;correlation=1234",
"x-mapped-tag" -> "value"
)
+
val context = httpPropagation.read(headerReaderFromMap(headers))
- context.tags should contain only(
- "hello" -> "world",
- "correlation" -> "1234",
- "mappedTag" -> "value"
- )
+ context.tags.get(plain("hello")) shouldBe "world"
+ context.tags.get(plain("correlation")) shouldBe "1234"
+ context.tags.get(plain("mappedTag")) shouldBe "value"
}
"handle errors when reading HTTP headers" in {
@@ -48,9 +49,9 @@ class HttpPropagationSpec extends WordSpec with Matchers with OptionValues {
context.get(HttpPropagationSpec.StringKey) shouldBe "hey"
context.get(HttpPropagationSpec.IntegerKey) shouldBe 123
context.get(HttpPropagationSpec.OptionalKey) shouldBe empty
- context.getTag("hello").value shouldBe "world"
- context.getTag("correlation").value shouldBe "1234"
- context.getTag("unknown") shouldBe empty
+ context.getTag(plain("hello")) shouldBe "world"
+ context.getTag(option("correlation")).value shouldBe "1234"
+ context.getTag(option("unknown")) shouldBe empty
}
}
@@ -64,10 +65,10 @@ class HttpPropagationSpec extends WordSpec with Matchers with OptionValues {
"write context tags when available" in {
val headers = mutable.Map.empty[String, String]
- val context = Context.of(Map(
+ val context = Context.of(TagSet.from(Map(
"hello" -> "world",
"mappedTag" -> "value"
- ))
+ )))
httpPropagation.write(context, headerWriterFromMap(headers))
headers should contain only(
diff --git a/kamon-core-tests/src/test/scala/kamon/instrumentation/HttpServerInstrumentationSpec.scala b/kamon-core-tests/src/test/scala/kamon/instrumentation/HttpServerInstrumentationSpec.scala
index 62eae45b..c7e856d0 100644
--- a/kamon-core-tests/src/test/scala/kamon/instrumentation/HttpServerInstrumentationSpec.scala
+++ b/kamon-core-tests/src/test/scala/kamon/instrumentation/HttpServerInstrumentationSpec.scala
@@ -4,6 +4,7 @@ import java.time.Duration
import kamon.context.Context
import kamon.metric.{Counter, Histogram, RangeSampler}
+import kamon.tag.Lookups._
import kamon.testkit.{MetricInspection, SpanInspection}
import org.scalatest.concurrent.Eventually
import org.scalatest.{Matchers, OptionValues, WordSpec}
@@ -20,10 +21,8 @@ class HttpServerInstrumentationSpec extends WordSpec with Matchers with SpanInsp
"custom-trace-id" -> "0011223344556677"
)))
- handler.context.tags should contain only(
- "tag" -> "value",
- "none" -> "0011223344556677"
- )
+ handler.context.tags.get(plain("tag")) shouldBe "value"
+ handler.context.tags.get(plain("none")) shouldBe "0011223344556677"
handler.send(fakeResponse(200, mutable.Map.empty), Context.Empty)
handler.doneSending(0L)
@@ -35,10 +34,8 @@ class HttpServerInstrumentationSpec extends WordSpec with Matchers with SpanInsp
"custom-trace-id" -> "0011223344556677"
)))
- handler.context.tags should contain only(
- "tag" -> "value",
- "none" -> "0011223344556677"
- )
+ handler.context.tags.get(plain("tag")) shouldBe "value"
+ handler.context.tags.get(plain("none")) shouldBe "0011223344556677"
val span = inspect(handler.span)
span.context().traceID.string shouldNot be("0011223344556677")
diff --git a/kamon-core-tests/src/test/scala/kamon/tag/TagSetSpec.scala b/kamon-core-tests/src/test/scala/kamon/tag/TagSetSpec.scala
new file mode 100644
index 00000000..cd23c58d
--- /dev/null
+++ b/kamon-core-tests/src/test/scala/kamon/tag/TagSetSpec.scala
@@ -0,0 +1,180 @@
+package kamon.tag
+
+import java.util.Optional
+
+import org.scalatest.{Matchers, WordSpec}
+
+import scala.collection.JavaConverters.mapAsJavaMapConverter
+
+class TagSetSpec extends WordSpec with Matchers {
+ import Lookups._
+
+ "Tags" should {
+ "silently drop null and unacceptable keys and/or values when constructed from the companion object builders" in {
+ TagSet.from(NullString, NullString).all().size shouldBe 0
+ TagSet.from(EmptyString, NullString).all().size shouldBe 0
+ TagSet.from(EmptyString, "value").all().size shouldBe 0
+ TagSet.from(NullString, "value").all().size shouldBe 0
+ TagSet.from("key", NullString).all().size shouldBe 0
+ TagSet.from("key", NullBoolean).all().size shouldBe 0
+ TagSet.from("key", NullLong).all().size shouldBe 0
+
+ TagSet.from(BadScalaTagMap).all().size shouldBe 0
+ TagSet.from(BadJavaTagMap).all().size shouldBe 0
+ }
+
+ "silently drop null keys and/or values when created with the .withTag, withTags or .and methods" in {
+ val tags = TagSet.from("initialKey", "initialValue")
+ .withTag(NullString, NullString)
+ .withTag(EmptyString, NullString)
+ .withTag(EmptyString, "value")
+ .withTag(NullString, "value")
+ .withTag("key", NullString)
+ .withTag("key", NullBoolean)
+ .withTag("key", NullLong)
+ .and(NullString, NullString)
+ .and(EmptyString, NullString)
+ .and(EmptyString, "value")
+ .and(NullString, "value")
+ .and("key", NullString)
+ .and("key", NullBoolean)
+ .and("key", NullLong)
+
+ tags.all().length shouldBe 1
+ tags.all().head.asInstanceOf[Tag.String].key shouldBe "initialKey"
+ tags.all().head.asInstanceOf[Tag.String].value shouldBe "initialValue"
+ }
+
+ "create a properly populated instance when valid pairs are provided" in {
+ TagSet.from("isAwesome", true).all().size shouldBe 1
+ TagSet.from("name", "kamon").all().size shouldBe 1
+ TagSet.from("age", 5L).all().size shouldBe 1
+
+ TagSet.from(GoodScalaTagMap).all().size shouldBe 3
+ TagSet.from(GoodJavaTagMap).all().size shouldBe 3
+
+ TagSet.from("initial", "initial")
+ .withTag("isAwesome", true)
+ .withTag("name", "Kamon")
+ .withTag("age", 5L)
+ .and("isAvailable", true)
+ .and("website", "kamon.io")
+ .and("supportedPlatforms", 1L)
+ .all().size shouldBe 7
+ }
+
+ "override pre-existent tags when merging with other Tags instance" in {
+ val leftTags = TagSet.from(GoodScalaTagMap)
+ val rightTags = TagSet
+ .from("name", "New Kamon")
+ .and("age", 42L)
+ .and("isAwesome", false) // just for testing :)
+
+ val tags = leftTags.withTags(rightTags)
+ tags.get(plain("name")) shouldBe "New Kamon"
+ tags.get(plainLong("age")) shouldBe 42L
+ tags.get(plainBoolean("isAwesome")) shouldBe false
+
+ val andTags = tags and leftTags
+ andTags.get(plain("name")) shouldBe "Kamon"
+ andTags.get(plainLong("age")) shouldBe 5L
+ andTags.get(plainBoolean("isAwesome")) shouldBe true
+ }
+
+ "provide typed access to the contained pairs when looking up values" in {
+ val tags = TagSet.from(GoodScalaTagMap)
+
+ tags.get(plain("name")) shouldBe "Kamon"
+ tags.get(plain("none")) shouldBe null
+ tags.get(option("name")) shouldBe Option("Kamon")
+ tags.get(option("none")) shouldBe None
+ tags.get(optional("name")) shouldBe Optional.of("Kamon")
+ tags.get(optional("none")) shouldBe Optional.empty()
+
+ tags.get(plainLong("age")) shouldBe 5L
+ tags.get(plainLong("nil")) shouldBe null
+ tags.get(longOption("age")) shouldBe Option(5L)
+ tags.get(longOption("nil")) shouldBe None
+ tags.get(longOptional("age")) shouldBe Optional.of(5L)
+ tags.get(longOptional("nil")) shouldBe Optional.empty()
+
+ tags.get(plainBoolean("isAwesome")) shouldBe true
+ tags.get(plainBoolean("isUnknown")) shouldBe null
+ tags.get(booleanOption("isAwesome")) shouldBe Some(true)
+ tags.get(booleanOption("isUnknown")) shouldBe None
+ tags.get(booleanOptional("isAwesome")) shouldBe Optional.of(true)
+ tags.get(booleanOptional("isUnknown")) shouldBe Optional.empty()
+
+ tags.get(coerce("age")) shouldBe "5"
+ tags.get(coerce("isAwesome")) shouldBe "true"
+ tags.get(coerce("unknown")) shouldBe "unknown"
+ }
+
+ "allow iterating over all contained tags" in {
+ val tags = TagSet.from(Map(
+ "age" -> 5L,
+ "name" -> "Kamon",
+ "isAwesome" -> true,
+ "hasTracing" -> true,
+ "website" -> "kamon.io",
+ "luckyNumber" -> 7L
+ ))
+
+ tags.iterator().length shouldBe 6
+ tags.iterator().find(matchPair("age", 5L)) shouldBe defined
+ tags.iterator().find(matchPair("luckyNumber", 7L)) shouldBe defined
+ tags.iterator().find(matchPair("hasTracing", true)) shouldBe defined
+ tags.iterator().find(matchPair("isAwesome", true)) shouldBe defined
+ tags.iterator().find(matchPair("website", "kamon.io")) shouldBe defined
+ tags.iterator().find(matchPair("name", "Kamon")) shouldBe defined
+ }
+
+ "be equal to other Tags instance with the same tags" in {
+ TagSet.from(GoodScalaTagMap) shouldBe TagSet.from(GoodScalaTagMap)
+ TagSet.from(GoodJavaTagMap) shouldBe TagSet.from(GoodJavaTagMap)
+ }
+
+ "have a readable toString implementation" in {
+ TagSet.from(GoodScalaTagMap).toString() should include("age=5")
+ TagSet.from(GoodScalaTagMap).toString() should include("name=Kamon")
+ TagSet.from(GoodScalaTagMap).toString() should include("isAwesome=true")
+ }
+ }
+
+ def matchPair(key: String, value: Any) = { tag: Tag => {
+ tag match {
+ case t: Tag.String => t.key == key && t.value == value
+ case t: Tag.Long => t.key == key && t.value == value
+ case t: Tag.Boolean => t.key == key && t.value == value
+ }
+
+ }}
+
+
+ val NullString: java.lang.String = null
+ val NullBoolean: java.lang.Boolean = NullString.asInstanceOf[java.lang.Boolean]
+ val NullLong: java.lang.Long = null
+ val EmptyString: java.lang.String = ""
+
+ val GoodScalaTagMap: Map[String, Any] = Map(
+ "age" -> 5L,
+ "name" -> "Kamon",
+ "isAwesome" -> true
+ )
+
+ val BadScalaTagMap: Map[String, Any] = Map(
+ NullString -> NullString,
+ EmptyString -> NullString,
+ NullString -> NullString,
+ EmptyString -> NullString,
+ EmptyString -> "value",
+ NullString -> "value",
+ "key" -> NullString,
+ "key" -> NullBoolean,
+ "key" -> NullLong
+ )
+
+ val GoodJavaTagMap = GoodScalaTagMap.asJava
+ val BadJavaTagMap = BadScalaTagMap.asJava
+
+}
diff --git a/kamon-core/src/main/colfer/Context.colf b/kamon-core/src/main/colfer/Context.colf
index f5d9a80b..9bd9ec76 100644
--- a/kamon-core/src/main/colfer/Context.colf
+++ b/kamon-core/src/main/colfer/Context.colf
@@ -1,11 +1,32 @@
package context
type Entry struct {
- name text
- content binary
+ key text
+ value binary
+}
+
+type StringTag struct {
+ key text
+ value text
+}
+
+type LongTag struct {
+ key text
+ value int64
+}
+
+type BooleanTag struct {
+ key text
+ value bool
+}
+
+type Tags struct {
+ strings []StringTag
+ longs []LongTag
+ booleans []BooleanTag
}
type Context struct {
- tags []text
+ tags Tags
entries []Entry
} \ No newline at end of file
diff --git a/kamon-core/src/main/java/kamon/context/generated/binary/context/BooleanTag.java b/kamon-core/src/main/java/kamon/context/generated/binary/context/BooleanTag.java
new file mode 100644
index 00000000..d8caad09
--- /dev/null
+++ b/kamon-core/src/main/java/kamon/context/generated/binary/context/BooleanTag.java
@@ -0,0 +1,412 @@
+package kamon.context.generated.binary.context;
+
+
+// Code generated by colf(1); DO NOT EDIT.
+
+
+import static java.lang.String.format;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamException;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.nio.charset.StandardCharsets;
+import java.util.InputMismatchException;
+import java.nio.BufferOverflowException;
+import java.nio.BufferUnderflowException;
+
+
+/**
+ * Data bean with built-in serialization support.
+
+ * @author generated by colf(1)
+ * @see <a href="https://github.com/pascaldekloe/colfer">Colfer's home</a>
+ */
+@javax.annotation.Generated(value="colf(1)", comments="Colfer from schema file Context.colf")
+public class BooleanTag implements Serializable {
+
+ /** The upper limit for serial byte sizes. */
+ public static int colferSizeMax = 16 * 1024 * 1024;
+
+
+
+
+ public String key;
+
+ public boolean value;
+
+
+ /** Default constructor */
+ public BooleanTag() {
+ init();
+ }
+
+
+ /** Colfer zero values. */
+ private void init() {
+ key = "";
+ }
+
+ /**
+ * {@link #reset(InputStream) Reusable} deserialization of Colfer streams.
+ */
+ public static class Unmarshaller {
+
+ /** The data source. */
+ protected InputStream in;
+
+ /** The read buffer. */
+ public byte[] buf;
+
+ /** The {@link #buf buffer}'s data start index, inclusive. */
+ protected int offset;
+
+ /** The {@link #buf buffer}'s data end index, exclusive. */
+ protected int i;
+
+
+ /**
+ * @param in the data source or {@code null}.
+ * @param buf the initial buffer or {@code null}.
+ */
+ public Unmarshaller(InputStream in, byte[] buf) {
+ // TODO: better size estimation
+ if (buf == null || buf.length == 0)
+ buf = new byte[Math.min(BooleanTag.colferSizeMax, 2048)];
+ this.buf = buf;
+ reset(in);
+ }
+
+ /**
+ * Reuses the marshaller.
+ * @param in the data source or {@code null}.
+ * @throws IllegalStateException on pending data.
+ */
+ public void reset(InputStream in) {
+ if (this.i != this.offset) throw new IllegalStateException("colfer: pending data");
+ this.in = in;
+ this.offset = 0;
+ this.i = 0;
+ }
+
+ /**
+ * Deserializes the following object.
+ * @return the result or {@code null} when EOF.
+ * @throws IOException from the input stream.
+ * @throws SecurityException on an upper limit breach defined by {@link #colferSizeMax}.
+ * @throws InputMismatchException when the data does not match this object's schema.
+ */
+ public BooleanTag next() throws IOException {
+ if (in == null) return null;
+
+ while (true) {
+ if (this.i > this.offset) {
+ try {
+ BooleanTag o = new BooleanTag();
+ this.offset = o.unmarshal(this.buf, this.offset, this.i);
+ return o;
+ } catch (BufferUnderflowException e) {
+ }
+ }
+ // not enough data
+
+ if (this.i <= this.offset) {
+ this.offset = 0;
+ this.i = 0;
+ } else if (i == buf.length) {
+ byte[] src = this.buf;
+ // TODO: better size estimation
+ if (offset == 0) this.buf = new byte[Math.min(BooleanTag.colferSizeMax, this.buf.length * 4)];
+ System.arraycopy(src, this.offset, this.buf, 0, this.i - this.offset);
+ this.i -= this.offset;
+ this.offset = 0;
+ }
+ assert this.i < this.buf.length;
+
+ int n = in.read(buf, i, buf.length - i);
+ if (n < 0) {
+ if (this.i > this.offset)
+ throw new InputMismatchException("colfer: pending data with EOF");
+ return null;
+ }
+ assert n > 0;
+ i += n;
+ }
+ }
+
+ }
+
+
+ /**
+ * Serializes the object.
+ * @param out the data destination.
+ * @param buf the initial buffer or {@code null}.
+ * @return the final buffer. When the serial fits into {@code buf} then the return is {@code buf}.
+ * Otherwise the return is a new buffer, large enough to hold the whole serial.
+ * @throws IOException from {@code out}.
+ * @throws IllegalStateException on an upper limit breach defined by {@link #colferSizeMax}.
+ */
+ public byte[] marshal(OutputStream out, byte[] buf) throws IOException {
+ // TODO: better size estimation
+ if (buf == null || buf.length == 0)
+ buf = new byte[Math.min(BooleanTag.colferSizeMax, 2048)];
+
+ while (true) {
+ int i;
+ try {
+ i = marshal(buf, 0);
+ } catch (BufferOverflowException e) {
+ buf = new byte[Math.min(BooleanTag.colferSizeMax, buf.length * 4)];
+ continue;
+ }
+
+ out.write(buf, 0, i);
+ return buf;
+ }
+ }
+
+ /**
+ * Serializes the object.
+ * @param buf the data destination.
+ * @param offset the initial index for {@code buf}, inclusive.
+ * @return the final index for {@code buf}, exclusive.
+ * @throws BufferOverflowException when {@code buf} is too small.
+ * @throws IllegalStateException on an upper limit breach defined by {@link #colferSizeMax}.
+ */
+ public int marshal(byte[] buf, int offset) {
+ int i = offset;
+
+ try {
+ if (! this.key.isEmpty()) {
+ buf[i++] = (byte) 0;
+ int start = ++i;
+
+ String s = this.key;
+ for (int sIndex = 0, sLength = s.length(); sIndex < sLength; sIndex++) {
+ char c = s.charAt(sIndex);
+ if (c < '\u0080') {
+ buf[i++] = (byte) c;
+ } else if (c < '\u0800') {
+ buf[i++] = (byte) (192 | c >>> 6);
+ buf[i++] = (byte) (128 | c & 63);
+ } else if (c < '\ud800' || c > '\udfff') {
+ buf[i++] = (byte) (224 | c >>> 12);
+ buf[i++] = (byte) (128 | c >>> 6 & 63);
+ buf[i++] = (byte) (128 | c & 63);
+ } else {
+ int cp = 0;
+ if (++sIndex < sLength) cp = Character.toCodePoint(c, s.charAt(sIndex));
+ if ((cp >= 1 << 16) && (cp < 1 << 21)) {
+ buf[i++] = (byte) (240 | cp >>> 18);
+ buf[i++] = (byte) (128 | cp >>> 12 & 63);
+ buf[i++] = (byte) (128 | cp >>> 6 & 63);
+ buf[i++] = (byte) (128 | cp & 63);
+ } else
+ buf[i++] = (byte) '?';
+ }
+ }
+ int size = i - start;
+ if (size > BooleanTag.colferSizeMax)
+ throw new IllegalStateException(format("colfer: kamon/context/generated/binary/context.BooleanTag.key size %d exceeds %d UTF-8 bytes", size, BooleanTag.colferSizeMax));
+
+ int ii = start - 1;
+ if (size > 0x7f) {
+ i++;
+ for (int x = size; x >= 1 << 14; x >>>= 7) i++;
+ System.arraycopy(buf, start, buf, i - size, size);
+
+ do {
+ buf[ii++] = (byte) (size | 0x80);
+ size >>>= 7;
+ } while (size > 0x7f);
+ }
+ buf[ii] = (byte) size;
+ }
+
+ if (this.value) {
+ buf[i++] = (byte) 1;
+ }
+
+ buf[i++] = (byte) 0x7f;
+ return i;
+ } catch (ArrayIndexOutOfBoundsException e) {
+ if (i - offset > BooleanTag.colferSizeMax)
+ throw new IllegalStateException(format("colfer: kamon/context/generated/binary/context.BooleanTag exceeds %d bytes", BooleanTag.colferSizeMax));
+ if (i > buf.length) throw new BufferOverflowException();
+ throw e;
+ }
+ }
+
+ /**
+ * Deserializes the object.
+ * @param buf the data source.
+ * @param offset the initial index for {@code buf}, inclusive.
+ * @return the final index for {@code buf}, exclusive.
+ * @throws BufferUnderflowException when {@code buf} is incomplete. (EOF)
+ * @throws SecurityException on an upper limit breach defined by {@link #colferSizeMax}.
+ * @throws InputMismatchException when the data does not match this object's schema.
+ */
+ public int unmarshal(byte[] buf, int offset) {
+ return unmarshal(buf, offset, buf.length);
+ }
+
+ /**
+ * Deserializes the object.
+ * @param buf the data source.
+ * @param offset the initial index for {@code buf}, inclusive.
+ * @param end the index limit for {@code buf}, exclusive.
+ * @return the final index for {@code buf}, exclusive.
+ * @throws BufferUnderflowException when {@code buf} is incomplete. (EOF)
+ * @throws SecurityException on an upper limit breach defined by {@link #colferSizeMax}.
+ * @throws InputMismatchException when the data does not match this object's schema.
+ */
+ public int unmarshal(byte[] buf, int offset, int end) {
+ if (end > buf.length) end = buf.length;
+ int i = offset;
+
+ try {
+ byte header = buf[i++];
+
+ if (header == (byte) 0) {
+ int size = 0;
+ for (int shift = 0; true; shift += 7) {
+ byte b = buf[i++];
+ size |= (b & 0x7f) << shift;
+ if (shift == 28 || b >= 0) break;
+ }
+ if (size < 0 || size > BooleanTag.colferSizeMax)
+ throw new SecurityException(format("colfer: kamon/context/generated/binary/context.BooleanTag.key size %d exceeds %d UTF-8 bytes", size, BooleanTag.colferSizeMax));
+
+ int start = i;
+ i += size;
+ this.key = new String(buf, start, size, StandardCharsets.UTF_8);
+ header = buf[i++];
+ }
+
+ if (header == (byte) 1) {
+ this.value = true;
+ header = buf[i++];
+ }
+
+ if (header != (byte) 0x7f)
+ throw new InputMismatchException(format("colfer: unknown header at byte %d", i - 1));
+ } finally {
+ if (i > end && end - offset < BooleanTag.colferSizeMax) throw new BufferUnderflowException();
+ if (i < 0 || i - offset > BooleanTag.colferSizeMax)
+ throw new SecurityException(format("colfer: kamon/context/generated/binary/context.BooleanTag exceeds %d bytes", BooleanTag.colferSizeMax));
+ if (i > end) throw new BufferUnderflowException();
+ }
+
+ return i;
+ }
+
+ // {@link Serializable} version number.
+ private static final long serialVersionUID = 2L;
+
+ // {@link Serializable} Colfer extension.
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ // TODO: better size estimation
+ byte[] buf = new byte[1024];
+ int n;
+ while (true) try {
+ n = marshal(buf, 0);
+ break;
+ } catch (BufferUnderflowException e) {
+ buf = new byte[4 * buf.length];
+ }
+
+ out.writeInt(n);
+ out.write(buf, 0, n);
+ }
+
+ // {@link Serializable} Colfer extension.
+ private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException {
+ init();
+
+ int n = in.readInt();
+ byte[] buf = new byte[n];
+ in.readFully(buf);
+ unmarshal(buf, 0);
+ }
+
+ // {@link Serializable} Colfer extension.
+ private void readObjectNoData() throws ObjectStreamException {
+ init();
+ }
+
+ /**
+ * Gets kamon/context/generated/binary/context.BooleanTag.key.
+ * @return the value.
+ */
+ public String getKey() {
+ return this.key;
+ }
+
+ /**
+ * Sets kamon/context/generated/binary/context.BooleanTag.key.
+ * @param value the replacement.
+ */
+ public void setKey(String value) {
+ this.key = value;
+ }
+
+ /**
+ * Sets kamon/context/generated/binary/context.BooleanTag.key.
+ * @param value the replacement.
+ * @return {link this}.
+ */
+ public BooleanTag withKey(String value) {
+ this.key = value;
+ return this;
+ }
+
+ /**
+ * Gets kamon/context/generated/binary/context.BooleanTag.value.
+ * @return the value.
+ */
+ public boolean getValue() {
+ return this.value;
+ }
+
+ /**
+ * Sets kamon/context/generated/binary/context.BooleanTag.value.
+ * @param value the replacement.
+ */
+ public void setValue(boolean value) {
+ this.value = value;
+ }
+
+ /**
+ * Sets kamon/context/generated/binary/context.BooleanTag.value.
+ * @param value the replacement.
+ * @return {link this}.
+ */
+ public BooleanTag withValue(boolean value) {
+ this.value = value;
+ return this;
+ }
+
+ @Override
+ public final int hashCode() {
+ int h = 1;
+ if (this.key != null) h = 31 * h + this.key.hashCode();
+ h = 31 * h + (this.value ? 1231 : 1237);
+ return h;
+ }
+
+ @Override
+ public final boolean equals(Object o) {
+ return o instanceof BooleanTag && equals((BooleanTag) o);
+ }
+
+ public final boolean equals(BooleanTag o) {
+ if (o == null) return false;
+ if (o == this) return true;
+ return o.getClass() == BooleanTag.class
+ && (this.key == null ? o.key == null : this.key.equals(o.key))
+ && this.value == o.value;
+ }
+
+}
diff --git a/kamon-core/src/main/java/kamon/context/generated/binary/context/Context.java b/kamon-core/src/main/java/kamon/context/generated/binary/context/Context.java
index 4be6d630..3582bfa2 100644
--- a/kamon-core/src/main/java/kamon/context/generated/binary/context/Context.java
+++ b/kamon-core/src/main/java/kamon/context/generated/binary/context/Context.java
@@ -12,7 +12,6 @@ import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.OutputStream;
import java.io.Serializable;
-import java.nio.charset.StandardCharsets;
import java.util.InputMismatchException;
import java.nio.BufferOverflowException;
import java.nio.BufferUnderflowException;
@@ -36,7 +35,7 @@ public class Context implements Serializable {
- public String[] tags;
+ public Tags tags;
public Entry[] entries;
@@ -46,12 +45,10 @@ public class Context implements Serializable {
init();
}
- private static final String[] _zeroTags = new String[0];
private static final Entry[] _zeroEntries = new Entry[0];
/** Colfer zero values. */
private void init() {
- tags = _zeroTags;
entries = _zeroEntries;
}
@@ -147,7 +144,6 @@ public class Context implements Serializable {
/**
* Serializes the object.
- * All {@code null} elements in {@link #tags} will be replaced with {@code ""}.
* All {@code null} elements in {@link #entries} will be replaced with a {@code new} value.
* @param out the data destination.
* @param buf the initial buffer or {@code null}.
@@ -177,7 +173,6 @@ public class Context implements Serializable {
/**
* Serializes the object.
- * All {@code null} elements in {@link #tags} will be replaced with {@code ""}.
* All {@code null} elements in {@link #entries} will be replaced with a {@code new} value.
* @param buf the data destination.
* @param offset the initial index for {@code buf}, inclusive.
@@ -189,68 +184,9 @@ public class Context implements Serializable {
int i = offset;
try {
- if (this.tags.length != 0) {
+ if (this.tags != null) {
buf[i++] = (byte) 0;
- String[] a = this.tags;
-
- int x = a.length;
- if (x > Context.colferListMax)
- throw new IllegalStateException(format("colfer: kamon/context/generated/binary/context.Context.tags length %d exceeds %d elements", x, Context.colferListMax));
- while (x > 0x7f) {
- buf[i++] = (byte) (x | 0x80);
- x >>>= 7;
- }
- buf[i++] = (byte) x;
-
- for (int ai = 0; ai < a.length; ai++) {
- String s = a[ai];
- if (s == null) {
- s = "";
- a[ai] = s;
- }
-
- int start = ++i;
-
- for (int sIndex = 0, sLength = s.length(); sIndex < sLength; sIndex++) {
- char c = s.charAt(sIndex);
- if (c < '\u0080') {
- buf[i++] = (byte) c;
- } else if (c < '\u0800') {
- buf[i++] = (byte) (192 | c >>> 6);
- buf[i++] = (byte) (128 | c & 63);
- } else if (c < '\ud800' || c > '\udfff') {
- buf[i++] = (byte) (224 | c >>> 12);
- buf[i++] = (byte) (128 | c >>> 6 & 63);
- buf[i++] = (byte) (128 | c & 63);
- } else {
- int cp = 0;
- if (++sIndex < sLength) cp = Character.toCodePoint(c, s.charAt(sIndex));
- if ((cp >= 1 << 16) && (cp < 1 << 21)) {
- buf[i++] = (byte) (240 | cp >>> 18);
- buf[i++] = (byte) (128 | cp >>> 12 & 63);
- buf[i++] = (byte) (128 | cp >>> 6 & 63);
- buf[i++] = (byte) (128 | cp & 63);
- } else
- buf[i++] = (byte) '?';
- }
- }
- int size = i - start;
- if (size > Context.colferSizeMax)
- throw new IllegalStateException(format("colfer: kamon/context/generated/binary/context.Context.tags[%d] size %d exceeds %d UTF-8 bytes", ai, size, Context.colferSizeMax));
-
- int ii = start - 1;
- if (size > 0x7f) {
- i++;
- for (int y = size; y >= 1 << 14; y >>>= 7) i++;
- System.arraycopy(buf, start, buf, i - size, size);
-
- do {
- buf[ii++] = (byte) (size | 0x80);
- size >>>= 7;
- } while (size > 0x7f);
- }
- buf[ii] = (byte) size;
- }
+ i = this.tags.marshal(buf, i);
}
if (this.entries.length != 0) {
@@ -317,31 +253,8 @@ public class Context implements Serializable {
byte header = buf[i++];
if (header == (byte) 0) {
- int length = 0;
- for (int shift = 0; true; shift += 7) {
- byte b = buf[i++];
- length |= (b & 0x7f) << shift;
- if (shift == 28 || b >= 0) break;
- }
- if (length < 0 || length > Context.colferListMax)
- throw new SecurityException(format("colfer: kamon/context/generated/binary/context.Context.tags length %d exceeds %d elements", length, Context.colferListMax));
-
- String[] a = new String[length];
- for (int ai = 0; ai < length; ai++) {
- int size = 0;
- for (int shift = 0; true; shift += 7) {
- byte b = buf[i++];
- size |= (b & 0x7f) << shift;
- if (shift == 28 || b >= 0) break;
- }
- if (size < 0 || size > Context.colferSizeMax)
- throw new SecurityException(format("colfer: kamon/context/generated/binary/context.Context.tags[%d] size %d exceeds %d UTF-8 bytes", ai, size, Context.colferSizeMax));
-
- int start = i;
- i += size;
- a[ai] = new String(buf, start, size, StandardCharsets.UTF_8);
- }
- this.tags = a;
+ this.tags = new Tags();
+ i = this.tags.unmarshal(buf, i, end);
header = buf[i++];
}
@@ -415,7 +328,7 @@ public class Context implements Serializable {
* Gets kamon/context/generated/binary/context.Context.tags.
* @return the value.
*/
- public String[] getTags() {
+ public Tags getTags() {
return this.tags;
}
@@ -423,7 +336,7 @@ public class Context implements Serializable {
* Sets kamon/context/generated/binary/context.Context.tags.
* @param value the replacement.
*/
- public void setTags(String[] value) {
+ public void setTags(Tags value) {
this.tags = value;
}
@@ -432,7 +345,7 @@ public class Context implements Serializable {
* @param value the replacement.
* @return {link this}.
*/
- public Context withTags(String[] value) {
+ public Context withTags(Tags value) {
this.tags = value;
return this;
}
@@ -466,7 +379,7 @@ public class Context implements Serializable {
@Override
public final int hashCode() {
int h = 1;
- for (String o : this.tags) h = 31 * h + (o == null ? 0 : o.hashCode());
+ if (this.tags != null) h = 31 * h + this.tags.hashCode();
for (Entry o : this.entries) h = 31 * h + (o == null ? 0 : o.hashCode());
return h;
}
@@ -480,7 +393,7 @@ public class Context implements Serializable {
if (o == null) return false;
if (o == this) return true;
return o.getClass() == Context.class
- && java.util.Arrays.equals(this.tags, o.tags)
+ && (this.tags == null ? o.tags == null : this.tags.equals(o.tags))
&& java.util.Arrays.equals(this.entries, o.entries);
}
diff --git a/kamon-core/src/main/java/kamon/context/generated/binary/context/Entry.java b/kamon-core/src/main/java/kamon/context/generated/binary/context/Entry.java
index dc75b10d..32213c79 100644
--- a/kamon-core/src/main/java/kamon/context/generated/binary/context/Entry.java
+++ b/kamon-core/src/main/java/kamon/context/generated/binary/context/Entry.java
@@ -33,9 +33,9 @@ public class Entry implements Serializable {
- public String name;
+ public String key;
- public byte[] content;
+ public byte[] value;
/** Default constructor */
@@ -47,8 +47,8 @@ public class Entry implements Serializable {
/** Colfer zero values. */
private void init() {
- name = "";
- content = _zeroBytes;
+ key = "";
+ value = _zeroBytes;
}
/**
@@ -181,11 +181,11 @@ public class Entry implements Serializable {
int i = offset;
try {
- if (! this.name.isEmpty()) {
+ if (! this.key.isEmpty()) {
buf[i++] = (byte) 0;
int start = ++i;
- String s = this.name;
+ String s = this.key;
for (int sIndex = 0, sLength = s.length(); sIndex < sLength; sIndex++) {
char c = s.charAt(sIndex);
if (c < '\u0080') {
@@ -211,7 +211,7 @@ public class Entry implements Serializable {
}
int size = i - start;
if (size > Entry.colferSizeMax)
- throw new IllegalStateException(format("colfer: kamon/context/generated/binary/context.Entry.name size %d exceeds %d UTF-8 bytes", size, Entry.colferSizeMax));
+ throw new IllegalStateException(format("colfer: kamon/context/generated/binary/context.Entry.key size %d exceeds %d UTF-8 bytes", size, Entry.colferSizeMax));
int ii = start - 1;
if (size > 0x7f) {
@@ -227,12 +227,12 @@ public class Entry implements Serializable {
buf[ii] = (byte) size;
}
- if (this.content.length != 0) {
+ if (this.value.length != 0) {
buf[i++] = (byte) 1;
- int size = this.content.length;
+ int size = this.value.length;
if (size > Entry.colferSizeMax)
- throw new IllegalStateException(format("colfer: kamon/context/generated/binary/context.Entry.content size %d exceeds %d bytes", size, Entry.colferSizeMax));
+ throw new IllegalStateException(format("colfer: kamon/context/generated/binary/context.Entry.value size %d exceeds %d bytes", size, Entry.colferSizeMax));
int x = size;
while (x > 0x7f) {
@@ -243,7 +243,7 @@ public class Entry implements Serializable {
int start = i;
i += size;
- System.arraycopy(this.content, 0, buf, start, size);
+ System.arraycopy(this.value, 0, buf, start, size);
}
buf[i++] = (byte) 0x7f;
@@ -294,11 +294,11 @@ public class Entry implements Serializable {
if (shift == 28 || b >= 0) break;
}
if (size < 0 || size > Entry.colferSizeMax)
- throw new SecurityException(format("colfer: kamon/context/generated/binary/context.Entry.name size %d exceeds %d UTF-8 bytes", size, Entry.colferSizeMax));
+ throw new SecurityException(format("colfer: kamon/context/generated/binary/context.Entry.key size %d exceeds %d UTF-8 bytes", size, Entry.colferSizeMax));
int start = i;
i += size;
- this.name = new String(buf, start, size, StandardCharsets.UTF_8);
+ this.key = new String(buf, start, size, StandardCharsets.UTF_8);
header = buf[i++];
}
@@ -310,12 +310,12 @@ public class Entry implements Serializable {
if (shift == 28 || b >= 0) break;
}
if (size < 0 || size > Entry.colferSizeMax)
- throw new SecurityException(format("colfer: kamon/context/generated/binary/context.Entry.content size %d exceeds %d bytes", size, Entry.colferSizeMax));
+ throw new SecurityException(format("colfer: kamon/context/generated/binary/context.Entry.value size %d exceeds %d bytes", size, Entry.colferSizeMax));
- this.content = new byte[size];
+ this.value = new byte[size];
int start = i;
i += size;
- System.arraycopy(buf, start, this.content, 0, size);
+ System.arraycopy(buf, start, this.value, 0, size);
header = buf[i++];
}
@@ -367,62 +367,62 @@ public class Entry implements Serializable {
}
/**
- * Gets kamon/context/generated/binary/context.Entry.name.
+ * Gets kamon/context/generated/binary/context.Entry.key.
* @return the value.
*/
- public String getName() {
- return this.name;
+ public String getKey() {
+ return this.key;
}
/**
- * Sets kamon/context/generated/binary/context.Entry.name.
+ * Sets kamon/context/generated/binary/context.Entry.key.
* @param value the replacement.
*/
- public void setName(String value) {
- this.name = value;
+ public void setKey(String value) {
+ this.key = value;
}
/**
- * Sets kamon/context/generated/binary/context.Entry.name.
+ * Sets kamon/context/generated/binary/context.Entry.key.
* @param value the replacement.
* @return {link this}.
*/
- public Entry withName(String value) {
- this.name = value;
+ public Entry withKey(String value) {
+ this.key = value;
return this;
}
/**
- * Gets kamon/context/generated/binary/context.Entry.content.
+ * Gets kamon/context/generated/binary/context.Entry.value.
* @return the value.
*/
- public byte[] getContent() {
- return this.content;
+ public byte[] getValue() {
+ return this.value;
}
/**
- * Sets kamon/context/generated/binary/context.Entry.content.
+ * Sets kamon/context/generated/binary/context.Entry.value.
* @param value the replacement.
*/
- public void setContent(byte[] value) {
- this.content = value;
+ public void setValue(byte[] value) {
+ this.value = value;
}
/**
- * Sets kamon/context/generated/binary/context.Entry.content.
+ * Sets kamon/context/generated/binary/context.Entry.value.
* @param value the replacement.
* @return {link this}.
*/
- public Entry withContent(byte[] value) {
- this.content = value;
+ public Entry withValue(byte[] value) {
+ this.value = value;
return this;
}
@Override
public final int hashCode() {
int h = 1;
- if (this.name != null) h = 31 * h + this.name.hashCode();
- for (byte b : this.content) h = 31 * h + b;
+ if (this.key != null) h = 31 * h + this.key.hashCode();
+ for (byte b : this.value) h = 31 * h + b;
return h;
}
@@ -435,8 +435,8 @@ public class Entry implements Serializable {
if (o == null) return false;
if (o == this) return true;
return o.getClass() == Entry.class
- && (this.name == null ? o.name == null : this.name.equals(o.name))
- && java.util.Arrays.equals(this.content, o.content);
+ && (this.key == null ? o.key == null : this.key.equals(o.key))
+ && java.util.Arrays.equals(this.value, o.value);
}
}
diff --git a/kamon-core/src/main/java/kamon/context/generated/binary/context/LongTag.java b/kamon-core/src/main/java/kamon/context/generated/binary/context/LongTag.java
new file mode 100644
index 00000000..505ba2f0
--- /dev/null
+++ b/kamon-core/src/main/java/kamon/context/generated/binary/context/LongTag.java
@@ -0,0 +1,443 @@
+package kamon.context.generated.binary.context;
+
+
+// Code generated by colf(1); DO NOT EDIT.
+
+
+import static java.lang.String.format;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamException;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.nio.charset.StandardCharsets;
+import java.util.InputMismatchException;
+import java.nio.BufferOverflowException;
+import java.nio.BufferUnderflowException;
+
+
+/**
+ * Data bean with built-in serialization support.
+
+ * @author generated by colf(1)
+ * @see <a href="https://github.com/pascaldekloe/colfer">Colfer's home</a>
+ */
+@javax.annotation.Generated(value="colf(1)", comments="Colfer from schema file Context.colf")
+public class LongTag implements Serializable {
+
+ /** The upper limit for serial byte sizes. */
+ public static int colferSizeMax = 16 * 1024 * 1024;
+
+
+
+
+ public String key;
+
+ public long value;
+
+
+ /** Default constructor */
+ public LongTag() {
+ init();
+ }
+
+
+ /** Colfer zero values. */
+ private void init() {
+ key = "";
+ }
+
+ /**
+ * {@link #reset(InputStream) Reusable} deserialization of Colfer streams.
+ */
+ public static class Unmarshaller {
+
+ /** The data source. */
+ protected InputStream in;
+
+ /** The read buffer. */
+ public byte[] buf;
+
+ /** The {@link #buf buffer}'s data start index, inclusive. */
+ protected int offset;
+
+ /** The {@link #buf buffer}'s data end index, exclusive. */
+ protected int i;
+
+
+ /**
+ * @param in the data source or {@code null}.
+ * @param buf the initial buffer or {@code null}.
+ */
+ public Unmarshaller(InputStream in, byte[] buf) {
+ // TODO: better size estimation
+ if (buf == null || buf.length == 0)
+ buf = new byte[Math.min(LongTag.colferSizeMax, 2048)];
+ this.buf = buf;
+ reset(in);
+ }
+
+ /**
+ * Reuses the marshaller.
+ * @param in the data source or {@code null}.
+ * @throws IllegalStateException on pending data.
+ */
+ public void reset(InputStream in) {
+ if (this.i != this.offset) throw new IllegalStateException("colfer: pending data");
+ this.in = in;
+ this.offset = 0;
+ this.i = 0;
+ }
+
+ /**
+ * Deserializes the following object.
+ * @return the result or {@code null} when EOF.
+ * @throws IOException from the input stream.
+ * @throws SecurityException on an upper limit breach defined by {@link #colferSizeMax}.
+ * @throws InputMismatchException when the data does not match this object's schema.
+ */
+ public LongTag next() throws IOException {
+ if (in == null) return null;
+
+ while (true) {
+ if (this.i > this.offset) {
+ try {
+ LongTag o = new LongTag();
+ this.offset = o.unmarshal(this.buf, this.offset, this.i);
+ return o;
+ } catch (BufferUnderflowException e) {
+ }
+ }
+ // not enough data
+
+ if (this.i <= this.offset) {
+ this.offset = 0;
+ this.i = 0;
+ } else if (i == buf.length) {
+ byte[] src = this.buf;
+ // TODO: better size estimation
+ if (offset == 0) this.buf = new byte[Math.min(LongTag.colferSizeMax, this.buf.length * 4)];
+ System.arraycopy(src, this.offset, this.buf, 0, this.i - this.offset);
+ this.i -= this.offset;
+ this.offset = 0;
+ }
+ assert this.i < this.buf.length;
+
+ int n = in.read(buf, i, buf.length - i);
+ if (n < 0) {
+ if (this.i > this.offset)
+ throw new InputMismatchException("colfer: pending data with EOF");
+ return null;
+ }
+ assert n > 0;
+ i += n;
+ }
+ }
+
+ }
+
+
+ /**
+ * Serializes the object.
+ * @param out the data destination.
+ * @param buf the initial buffer or {@code null}.
+ * @return the final buffer. When the serial fits into {@code buf} then the return is {@code buf}.
+ * Otherwise the return is a new buffer, large enough to hold the whole serial.
+ * @throws IOException from {@code out}.
+ * @throws IllegalStateException on an upper limit breach defined by {@link #colferSizeMax}.
+ */
+ public byte[] marshal(OutputStream out, byte[] buf) throws IOException {
+ // TODO: better size estimation
+ if (buf == null || buf.length == 0)
+ buf = new byte[Math.min(LongTag.colferSizeMax, 2048)];
+
+ while (true) {
+ int i;
+ try {
+ i = marshal(buf, 0);
+ } catch (BufferOverflowException e) {
+ buf = new byte[Math.min(LongTag.colferSizeMax, buf.length * 4)];
+ continue;
+ }
+
+ out.write(buf, 0, i);
+ return buf;
+ }
+ }
+
+ /**
+ * Serializes the object.
+ * @param buf the data destination.
+ * @param offset the initial index for {@code buf}, inclusive.
+ * @return the final index for {@code buf}, exclusive.
+ * @throws BufferOverflowException when {@code buf} is too small.
+ * @throws IllegalStateException on an upper limit breach defined by {@link #colferSizeMax}.
+ */
+ public int marshal(byte[] buf, int offset) {
+ int i = offset;
+
+ try {
+ if (! this.key.isEmpty()) {
+ buf[i++] = (byte) 0;
+ int start = ++i;
+
+ String s = this.key;
+ for (int sIndex = 0, sLength = s.length(); sIndex < sLength; sIndex++) {
+ char c = s.charAt(sIndex);
+ if (c < '\u0080') {
+ buf[i++] = (byte) c;
+ } else if (c < '\u0800') {
+ buf[i++] = (byte) (192 | c >>> 6);
+ buf[i++] = (byte) (128 | c & 63);
+ } else if (c < '\ud800' || c > '\udfff') {
+ buf[i++] = (byte) (224 | c >>> 12);
+ buf[i++] = (byte) (128 | c >>> 6 & 63);
+ buf[i++] = (byte) (128 | c & 63);
+ } else {
+ int cp = 0;
+ if (++sIndex < sLength) cp = Character.toCodePoint(c, s.charAt(sIndex));
+ if ((cp >= 1 << 16) && (cp < 1 << 21)) {
+ buf[i++] = (byte) (240 | cp >>> 18);
+ buf[i++] = (byte) (128 | cp >>> 12 & 63);
+ buf[i++] = (byte) (128 | cp >>> 6 & 63);
+ buf[i++] = (byte) (128 | cp & 63);
+ } else
+ buf[i++] = (byte) '?';
+ }
+ }
+ int size = i - start;
+ if (size > LongTag.colferSizeMax)
+ throw new IllegalStateException(format("colfer: kamon/context/generated/binary/context.LongTag.key size %d exceeds %d UTF-8 bytes", size, LongTag.colferSizeMax));
+
+ int ii = start - 1;
+ if (size > 0x7f) {
+ i++;
+ for (int x = size; x >= 1 << 14; x >>>= 7) i++;
+ System.arraycopy(buf, start, buf, i - size, size);
+
+ do {
+ buf[ii++] = (byte) (size | 0x80);
+ size >>>= 7;
+ } while (size > 0x7f);
+ }
+ buf[ii] = (byte) size;
+ }
+
+ if (this.value != 0) {
+ long x = this.value;
+ if (x < 0) {
+ x = -x;
+ buf[i++] = (byte) (1 | 0x80);
+ } else
+ buf[i++] = (byte) 1;
+ for (int n = 0; n < 8 && (x & ~0x7fL) != 0; n++) {
+ buf[i++] = (byte) (x | 0x80);
+ x >>>= 7;
+ }
+ buf[i++] = (byte) x;
+ }
+
+ buf[i++] = (byte) 0x7f;
+ return i;
+ } catch (ArrayIndexOutOfBoundsException e) {
+ if (i - offset > LongTag.colferSizeMax)
+ throw new IllegalStateException(format("colfer: kamon/context/generated/binary/context.LongTag exceeds %d bytes", LongTag.colferSizeMax));
+ if (i > buf.length) throw new BufferOverflowException();
+ throw e;
+ }
+ }
+
+ /**
+ * Deserializes the object.
+ * @param buf the data source.
+ * @param offset the initial index for {@code buf}, inclusive.
+ * @return the final index for {@code buf}, exclusive.
+ * @throws BufferUnderflowException when {@code buf} is incomplete. (EOF)
+ * @throws SecurityException on an upper limit breach defined by {@link #colferSizeMax}.
+ * @throws InputMismatchException when the data does not match this object's schema.
+ */
+ public int unmarshal(byte[] buf, int offset) {
+ return unmarshal(buf, offset, buf.length);
+ }
+
+ /**
+ * Deserializes the object.
+ * @param buf the data source.
+ * @param offset the initial index for {@code buf}, inclusive.
+ * @param end the index limit for {@code buf}, exclusive.
+ * @return the final index for {@code buf}, exclusive.
+ * @throws BufferUnderflowException when {@code buf} is incomplete. (EOF)
+ * @throws SecurityException on an upper limit breach defined by {@link #colferSizeMax}.
+ * @throws InputMismatchException when the data does not match this object's schema.
+ */
+ public int unmarshal(byte[] buf, int offset, int end) {
+ if (end > buf.length) end = buf.length;
+ int i = offset;
+
+ try {
+ byte header = buf[i++];
+
+ if (header == (byte) 0) {
+ int size = 0;
+ for (int shift = 0; true; shift += 7) {
+ byte b = buf[i++];
+ size |= (b & 0x7f) << shift;
+ if (shift == 28 || b >= 0) break;
+ }
+ if (size < 0 || size > LongTag.colferSizeMax)
+ throw new SecurityException(format("colfer: kamon/context/generated/binary/context.LongTag.key size %d exceeds %d UTF-8 bytes", size, LongTag.colferSizeMax));
+
+ int start = i;
+ i += size;
+ this.key = new String(buf, start, size, StandardCharsets.UTF_8);
+ header = buf[i++];
+ }
+
+ if (header == (byte) 1) {
+ long x = 0;
+ for (int shift = 0; true; shift += 7) {
+ byte b = buf[i++];
+ if (shift == 56 || b >= 0) {
+ x |= (b & 0xffL) << shift;
+ break;
+ }
+ x |= (b & 0x7fL) << shift;
+ }
+ this.value = x;
+ header = buf[i++];
+ } else if (header == (byte) (1 | 0x80)) {
+ long x = 0;
+ for (int shift = 0; true; shift += 7) {
+ byte b = buf[i++];
+ if (shift == 56 || b >= 0) {
+ x |= (b & 0xffL) << shift;
+ break;
+ }
+ x |= (b & 0x7fL) << shift;
+ }
+ this.value = -x;
+ header = buf[i++];
+ }
+
+ if (header != (byte) 0x7f)
+ throw new InputMismatchException(format("colfer: unknown header at byte %d", i - 1));
+ } finally {
+ if (i > end && end - offset < LongTag.colferSizeMax) throw new BufferUnderflowException();
+ if (i < 0 || i - offset > LongTag.colferSizeMax)
+ throw new SecurityException(format("colfer: kamon/context/generated/binary/context.LongTag exceeds %d bytes", LongTag.colferSizeMax));
+ if (i > end) throw new BufferUnderflowException();
+ }
+
+ return i;
+ }
+
+ // {@link Serializable} version number.
+ private static final long serialVersionUID = 2L;
+
+ // {@link Serializable} Colfer extension.
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ // TODO: better size estimation
+ byte[] buf = new byte[1024];
+ int n;
+ while (true) try {
+ n = marshal(buf, 0);
+ break;
+ } catch (BufferUnderflowException e) {
+ buf = new byte[4 * buf.length];
+ }
+
+ out.writeInt(n);
+ out.write(buf, 0, n);
+ }
+
+ // {@link Serializable} Colfer extension.
+ private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException {
+ init();
+
+ int n = in.readInt();
+ byte[] buf = new byte[n];
+ in.readFully(buf);
+ unmarshal(buf, 0);
+ }
+
+ // {@link Serializable} Colfer extension.
+ private void readObjectNoData() throws ObjectStreamException {
+ init();
+ }
+
+ /**
+ * Gets kamon/context/generated/binary/context.LongTag.key.
+ * @return the value.
+ */
+ public String getKey() {
+ return this.key;
+ }
+
+ /**
+ * Sets kamon/context/generated/binary/context.LongTag.key.
+ * @param value the replacement.
+ */
+ public void setKey(String value) {
+ this.key = value;
+ }
+
+ /**
+ * Sets kamon/context/generated/binary/context.LongTag.key.
+ * @param value the replacement.
+ * @return {link this}.
+ */
+ public LongTag withKey(String value) {
+ this.key = value;
+ return this;
+ }
+
+ /**
+ * Gets kamon/context/generated/binary/context.LongTag.value.
+ * @return the value.
+ */
+ public long getValue() {
+ return this.value;
+ }
+
+ /**
+ * Sets kamon/context/generated/binary/context.LongTag.value.
+ * @param value the replacement.
+ */
+ public void setValue(long value) {
+ this.value = value;
+ }
+
+ /**
+ * Sets kamon/context/generated/binary/context.LongTag.value.
+ * @param value the replacement.
+ * @return {link this}.
+ */
+ public LongTag withValue(long value) {
+ this.value = value;
+ return this;
+ }
+
+ @Override
+ public final int hashCode() {
+ int h = 1;
+ if (this.key != null) h = 31 * h + this.key.hashCode();
+ h = 31 * h + (int)(this.value ^ this.value >>> 32);
+ return h;
+ }
+
+ @Override
+ public final boolean equals(Object o) {
+ return o instanceof LongTag && equals((LongTag) o);
+ }
+
+ public final boolean equals(LongTag o) {
+ if (o == null) return false;
+ if (o == this) return true;
+ return o.getClass() == LongTag.class
+ && (this.key == null ? o.key == null : this.key.equals(o.key))
+ && this.value == o.value;
+ }
+
+}
diff --git a/kamon-core/src/main/java/kamon/context/generated/binary/context/StringTag.java b/kamon-core/src/main/java/kamon/context/generated/binary/context/StringTag.java
new file mode 100644
index 00000000..366744b4
--- /dev/null
+++ b/kamon-core/src/main/java/kamon/context/generated/binary/context/StringTag.java
@@ -0,0 +1,466 @@
+package kamon.context.generated.binary.context;
+
+
+// Code generated by colf(1); DO NOT EDIT.
+
+
+import static java.lang.String.format;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamException;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.nio.charset.StandardCharsets;
+import java.util.InputMismatchException;
+import java.nio.BufferOverflowException;
+import java.nio.BufferUnderflowException;
+
+
+/**
+ * Data bean with built-in serialization support.
+
+ * @author generated by colf(1)
+ * @see <a href="https://github.com/pascaldekloe/colfer">Colfer's home</a>
+ */
+@javax.annotation.Generated(value="colf(1)", comments="Colfer from schema file Context.colf")
+public class StringTag implements Serializable {
+
+ /** The upper limit for serial byte sizes. */
+ public static int colferSizeMax = 16 * 1024 * 1024;
+
+
+
+
+ public String key;
+
+ public String value;
+
+
+ /** Default constructor */
+ public StringTag() {
+ init();
+ }
+
+
+ /** Colfer zero values. */
+ private void init() {
+ key = "";
+ value = "";
+ }
+
+ /**
+ * {@link #reset(InputStream) Reusable} deserialization of Colfer streams.
+ */
+ public static class Unmarshaller {
+
+ /** The data source. */
+ protected InputStream in;
+
+ /** The read buffer. */
+ public byte[] buf;
+
+ /** The {@link #buf buffer}'s data start index, inclusive. */
+ protected int offset;
+
+ /** The {@link #buf buffer}'s data end index, exclusive. */
+ protected int i;
+
+
+ /**
+ * @param in the data source or {@code null}.
+ * @param buf the initial buffer or {@code null}.
+ */
+ public Unmarshaller(InputStream in, byte[] buf) {
+ // TODO: better size estimation
+ if (buf == null || buf.length == 0)
+ buf = new byte[Math.min(StringTag.colferSizeMax, 2048)];
+ this.buf = buf;
+ reset(in);
+ }
+
+ /**
+ * Reuses the marshaller.
+ * @param in the data source or {@code null}.
+ * @throws IllegalStateException on pending data.
+ */
+ public void reset(InputStream in) {
+ if (this.i != this.offset) throw new IllegalStateException("colfer: pending data");
+ this.in = in;
+ this.offset = 0;
+ this.i = 0;
+ }
+
+ /**
+ * Deserializes the following object.
+ * @return the result or {@code null} when EOF.
+ * @throws IOException from the input stream.
+ * @throws SecurityException on an upper limit breach defined by {@link #colferSizeMax}.
+ * @throws InputMismatchException when the data does not match this object's schema.
+ */
+ public StringTag next() throws IOException {
+ if (in == null) return null;
+
+ while (true) {
+ if (this.i > this.offset) {
+ try {
+ StringTag o = new StringTag();
+ this.offset = o.unmarshal(this.buf, this.offset, this.i);
+ return o;
+ } catch (BufferUnderflowException e) {
+ }
+ }
+ // not enough data
+
+ if (this.i <= this.offset) {
+ this.offset = 0;
+ this.i = 0;
+ } else if (i == buf.length) {
+ byte[] src = this.buf;
+ // TODO: better size estimation
+ if (offset == 0) this.buf = new byte[Math.min(StringTag.colferSizeMax, this.buf.length * 4)];
+ System.arraycopy(src, this.offset, this.buf, 0, this.i - this.offset);
+ this.i -= this.offset;
+ this.offset = 0;
+ }
+ assert this.i < this.buf.length;
+
+ int n = in.read(buf, i, buf.length - i);
+ if (n < 0) {
+ if (this.i > this.offset)
+ throw new InputMismatchException("colfer: pending data with EOF");
+ return null;
+ }
+ assert n > 0;
+ i += n;
+ }
+ }
+
+ }
+
+
+ /**
+ * Serializes the object.
+ * @param out the data destination.
+ * @param buf the initial buffer or {@code null}.
+ * @return the final buffer. When the serial fits into {@code buf} then the return is {@code buf}.
+ * Otherwise the return is a new buffer, large enough to hold the whole serial.
+ * @throws IOException from {@code out}.
+ * @throws IllegalStateException on an upper limit breach defined by {@link #colferSizeMax}.
+ */
+ public byte[] marshal(OutputStream out, byte[] buf) throws IOException {
+ // TODO: better size estimation
+ if (buf == null || buf.length == 0)
+ buf = new byte[Math.min(StringTag.colferSizeMax, 2048)];
+
+ while (true) {
+ int i;
+ try {
+ i = marshal(buf, 0);
+ } catch (BufferOverflowException e) {
+ buf = new byte[Math.min(StringTag.colferSizeMax, buf.length * 4)];
+ continue;
+ }
+
+ out.write(buf, 0, i);
+ return buf;
+ }
+ }
+
+ /**
+ * Serializes the object.
+ * @param buf the data destination.
+ * @param offset the initial index for {@code buf}, inclusive.
+ * @return the final index for {@code buf}, exclusive.
+ * @throws BufferOverflowException when {@code buf} is too small.
+ * @throws IllegalStateException on an upper limit breach defined by {@link #colferSizeMax}.
+ */
+ public int marshal(byte[] buf, int offset) {
+ int i = offset;
+
+ try {
+ if (! this.key.isEmpty()) {
+ buf[i++] = (byte) 0;
+ int start = ++i;
+
+ String s = this.key;
+ for (int sIndex = 0, sLength = s.length(); sIndex < sLength; sIndex++) {
+ char c = s.charAt(sIndex);
+ if (c < '\u0080') {
+ buf[i++] = (byte) c;
+ } else if (c < '\u0800') {
+ buf[i++] = (byte) (192 | c >>> 6);
+ buf[i++] = (byte) (128 | c & 63);
+ } else if (c < '\ud800' || c > '\udfff') {
+ buf[i++] = (byte) (224 | c >>> 12);
+ buf[i++] = (byte) (128 | c >>> 6 & 63);
+ buf[i++] = (byte) (128 | c & 63);
+ } else {
+ int cp = 0;
+ if (++sIndex < sLength) cp = Character.toCodePoint(c, s.charAt(sIndex));
+ if ((cp >= 1 << 16) && (cp < 1 << 21)) {
+ buf[i++] = (byte) (240 | cp >>> 18);
+ buf[i++] = (byte) (128 | cp >>> 12 & 63);
+ buf[i++] = (byte) (128 | cp >>> 6 & 63);
+ buf[i++] = (byte) (128 | cp & 63);
+ } else
+ buf[i++] = (byte) '?';
+ }
+ }
+ int size = i - start;
+ if (size > StringTag.colferSizeMax)
+ throw new IllegalStateException(format("colfer: kamon/context/generated/binary/context.StringTag.key size %d exceeds %d UTF-8 bytes", size, StringTag.colferSizeMax));
+
+ int ii = start - 1;
+ if (size > 0x7f) {
+ i++;
+ for (int x = size; x >= 1 << 14; x >>>= 7) i++;
+ System.arraycopy(buf, start, buf, i - size, size);
+
+ do {
+ buf[ii++] = (byte) (size | 0x80);
+ size >>>= 7;
+ } while (size > 0x7f);
+ }
+ buf[ii] = (byte) size;
+ }
+
+ if (! this.value.isEmpty()) {
+ buf[i++] = (byte) 1;
+ int start = ++i;
+
+ String s = this.value;
+ for (int sIndex = 0, sLength = s.length(); sIndex < sLength; sIndex++) {
+ char c = s.charAt(sIndex);
+ if (c < '\u0080') {
+ buf[i++] = (byte) c;
+ } else if (c < '\u0800') {
+ buf[i++] = (byte) (192 | c >>> 6);
+ buf[i++] = (byte) (128 | c & 63);
+ } else if (c < '\ud800' || c > '\udfff') {
+ buf[i++] = (byte) (224 | c >>> 12);
+ buf[i++] = (byte) (128 | c >>> 6 & 63);
+ buf[i++] = (byte) (128 | c & 63);
+ } else {
+ int cp = 0;
+ if (++sIndex < sLength) cp = Character.toCodePoint(c, s.charAt(sIndex));
+ if ((cp >= 1 << 16) && (cp < 1 << 21)) {
+ buf[i++] = (byte) (240 | cp >>> 18);
+ buf[i++] = (byte) (128 | cp >>> 12 & 63);
+ buf[i++] = (byte) (128 | cp >>> 6 & 63);
+ buf[i++] = (byte) (128 | cp & 63);
+ } else
+ buf[i++] = (byte) '?';
+ }
+ }
+ int size = i - start;
+ if (size > StringTag.colferSizeMax)
+ throw new IllegalStateException(format("colfer: kamon/context/generated/binary/context.StringTag.value size %d exceeds %d UTF-8 bytes", size, StringTag.colferSizeMax));
+
+ int ii = start - 1;
+ if (size > 0x7f) {
+ i++;
+ for (int x = size; x >= 1 << 14; x >>>= 7) i++;
+ System.arraycopy(buf, start, buf, i - size, size);
+
+ do {
+ buf[ii++] = (byte) (size | 0x80);
+ size >>>= 7;
+ } while (size > 0x7f);
+ }
+ buf[ii] = (byte) size;
+ }
+
+ buf[i++] = (byte) 0x7f;
+ return i;
+ } catch (ArrayIndexOutOfBoundsException e) {
+ if (i - offset > StringTag.colferSizeMax)
+ throw new IllegalStateException(format("colfer: kamon/context/generated/binary/context.StringTag exceeds %d bytes", StringTag.colferSizeMax));
+ if (i > buf.length) throw new BufferOverflowException();
+ throw e;
+ }
+ }
+
+ /**
+ * Deserializes the object.
+ * @param buf the data source.
+ * @param offset the initial index for {@code buf}, inclusive.
+ * @return the final index for {@code buf}, exclusive.
+ * @throws BufferUnderflowException when {@code buf} is incomplete. (EOF)
+ * @throws SecurityException on an upper limit breach defined by {@link #colferSizeMax}.
+ * @throws InputMismatchException when the data does not match this object's schema.
+ */
+ public int unmarshal(byte[] buf, int offset) {
+ return unmarshal(buf, offset, buf.length);
+ }
+
+ /**
+ * Deserializes the object.
+ * @param buf the data source.
+ * @param offset the initial index for {@code buf}, inclusive.
+ * @param end the index limit for {@code buf}, exclusive.
+ * @return the final index for {@code buf}, exclusive.
+ * @throws BufferUnderflowException when {@code buf} is incomplete. (EOF)
+ * @throws SecurityException on an upper limit breach defined by {@link #colferSizeMax}.
+ * @throws InputMismatchException when the data does not match this object's schema.
+ */
+ public int unmarshal(byte[] buf, int offset, int end) {
+ if (end > buf.length) end = buf.length;
+ int i = offset;
+
+ try {
+ byte header = buf[i++];
+
+ if (header == (byte) 0) {
+ int size = 0;
+ for (int shift = 0; true; shift += 7) {
+ byte b = buf[i++];
+ size |= (b & 0x7f) << shift;
+ if (shift == 28 || b >= 0) break;
+ }
+ if (size < 0 || size > StringTag.colferSizeMax)
+ throw new SecurityException(format("colfer: kamon/context/generated/binary/context.StringTag.key size %d exceeds %d UTF-8 bytes", size, StringTag.colferSizeMax));
+
+ int start = i;
+ i += size;
+ this.key = new String(buf, start, size, StandardCharsets.UTF_8);
+ header = buf[i++];
+ }
+
+ if (header == (byte) 1) {
+ int size = 0;
+ for (int shift = 0; true; shift += 7) {
+ byte b = buf[i++];
+ size |= (b & 0x7f) << shift;
+ if (shift == 28 || b >= 0) break;
+ }
+ if (size < 0 || size > StringTag.colferSizeMax)
+ throw new SecurityException(format("colfer: kamon/context/generated/binary/context.StringTag.value size %d exceeds %d UTF-8 bytes", size, StringTag.colferSizeMax));
+
+ int start = i;
+ i += size;
+ this.value = new String(buf, start, size, StandardCharsets.UTF_8);
+ header = buf[i++];
+ }
+
+ if (header != (byte) 0x7f)
+ throw new InputMismatchException(format("colfer: unknown header at byte %d", i - 1));
+ } finally {
+ if (i > end && end - offset < StringTag.colferSizeMax) throw new BufferUnderflowException();
+ if (i < 0 || i - offset > StringTag.colferSizeMax)
+ throw new SecurityException(format("colfer: kamon/context/generated/binary/context.StringTag exceeds %d bytes", StringTag.colferSizeMax));
+ if (i > end) throw new BufferUnderflowException();
+ }
+
+ return i;
+ }
+
+ // {@link Serializable} version number.
+ private static final long serialVersionUID = 2L;
+
+ // {@link Serializable} Colfer extension.
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ // TODO: better size estimation
+ byte[] buf = new byte[1024];
+ int n;
+ while (true) try {
+ n = marshal(buf, 0);
+ break;
+ } catch (BufferUnderflowException e) {
+ buf = new byte[4 * buf.length];
+ }
+
+ out.writeInt(n);
+ out.write(buf, 0, n);
+ }
+
+ // {@link Serializable} Colfer extension.
+ private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException {
+ init();
+
+ int n = in.readInt();
+ byte[] buf = new byte[n];
+ in.readFully(buf);
+ unmarshal(buf, 0);
+ }
+
+ // {@link Serializable} Colfer extension.
+ private void readObjectNoData() throws ObjectStreamException {
+ init();
+ }
+
+ /**
+ * Gets kamon/context/generated/binary/context.StringTag.key.
+ * @return the value.
+ */
+ public String getKey() {
+ return this.key;
+ }
+
+ /**
+ * Sets kamon/context/generated/binary/context.StringTag.key.
+ * @param value the replacement.
+ */
+ public void setKey(String value) {
+ this.key = value;
+ }
+
+ /**
+ * Sets kamon/context/generated/binary/context.StringTag.key.
+ * @param value the replacement.
+ * @return {link this}.
+ */
+ public StringTag withKey(String value) {
+ this.key = value;
+ return this;
+ }
+
+ /**
+ * Gets kamon/context/generated/binary/context.StringTag.value.
+ * @return the value.
+ */
+ public String getValue() {
+ return this.value;
+ }
+
+ /**
+ * Sets kamon/context/generated/binary/context.StringTag.value.
+ * @param value the replacement.
+ */
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ /**
+ * Sets kamon/context/generated/binary/context.StringTag.value.
+ * @param value the replacement.
+ * @return {link this}.
+ */
+ public StringTag withValue(String value) {
+ this.value = value;
+ return this;
+ }
+
+ @Override
+ public final int hashCode() {
+ int h = 1;
+ if (this.key != null) h = 31 * h + this.key.hashCode();
+ if (this.value != null) h = 31 * h + this.value.hashCode();
+ return h;
+ }
+
+ @Override
+ public final boolean equals(Object o) {
+ return o instanceof StringTag && equals((StringTag) o);
+ }
+
+ public final boolean equals(StringTag o) {
+ if (o == null) return false;
+ if (o == this) return true;
+ return o.getClass() == StringTag.class
+ && (this.key == null ? o.key == null : this.key.equals(o.key))
+ && (this.value == null ? o.value == null : this.value.equals(o.value));
+ }
+
+}
diff --git a/kamon-core/src/main/java/kamon/context/generated/binary/context/Tags.java b/kamon-core/src/main/java/kamon/context/generated/binary/context/Tags.java
new file mode 100644
index 00000000..ce4d66db
--- /dev/null
+++ b/kamon-core/src/main/java/kamon/context/generated/binary/context/Tags.java
@@ -0,0 +1,513 @@
+package kamon.context.generated.binary.context;
+
+
+// Code generated by colf(1); DO NOT EDIT.
+
+
+import static java.lang.String.format;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamException;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.util.InputMismatchException;
+import java.nio.BufferOverflowException;
+import java.nio.BufferUnderflowException;
+
+
+/**
+ * Data bean with built-in serialization support.
+
+ * @author generated by colf(1)
+ * @see <a href="https://github.com/pascaldekloe/colfer">Colfer's home</a>
+ */
+@javax.annotation.Generated(value="colf(1)", comments="Colfer from schema file Context.colf")
+public class Tags implements Serializable {
+
+ /** The upper limit for serial byte sizes. */
+ public static int colferSizeMax = 16 * 1024 * 1024;
+
+ /** The upper limit for the number of elements in a list. */
+ public static int colferListMax = 64 * 1024;
+
+
+
+
+ public StringTag[] strings;
+
+ public LongTag[] longs;
+
+ public BooleanTag[] booleans;
+
+
+ /** Default constructor */
+ public Tags() {
+ init();
+ }
+
+ private static final StringTag[] _zeroStrings = new StringTag[0];
+ private static final LongTag[] _zeroLongs = new LongTag[0];
+ private static final BooleanTag[] _zeroBooleans = new BooleanTag[0];
+
+ /** Colfer zero values. */
+ private void init() {
+ strings = _zeroStrings;
+ longs = _zeroLongs;
+ booleans = _zeroBooleans;
+ }
+
+ /**
+ * {@link #reset(InputStream) Reusable} deserialization of Colfer streams.
+ */
+ public static class Unmarshaller {
+
+ /** The data source. */
+ protected InputStream in;
+
+ /** The read buffer. */
+ public byte[] buf;
+
+ /** The {@link #buf buffer}'s data start index, inclusive. */
+ protected int offset;
+
+ /** The {@link #buf buffer}'s data end index, exclusive. */
+ protected int i;
+
+
+ /**
+ * @param in the data source or {@code null}.
+ * @param buf the initial buffer or {@code null}.
+ */
+ public Unmarshaller(InputStream in, byte[] buf) {
+ // TODO: better size estimation
+ if (buf == null || buf.length == 0)
+ buf = new byte[Math.min(Tags.colferSizeMax, 2048)];
+ this.buf = buf;
+ reset(in);
+ }
+
+ /**
+ * Reuses the marshaller.
+ * @param in the data source or {@code null}.
+ * @throws IllegalStateException on pending data.
+ */
+ public void reset(InputStream in) {
+ if (this.i != this.offset) throw new IllegalStateException("colfer: pending data");
+ this.in = in;
+ this.offset = 0;
+ this.i = 0;
+ }
+
+ /**
+ * Deserializes the following object.
+ * @return the result or {@code null} when EOF.
+ * @throws IOException from the input stream.
+ * @throws SecurityException on an upper limit breach defined by either {@link #colferSizeMax} or {@link #colferListMax}.
+ * @throws InputMismatchException when the data does not match this object's schema.
+ */
+ public Tags next() throws IOException {
+ if (in == null) return null;
+
+ while (true) {
+ if (this.i > this.offset) {
+ try {
+ Tags o = new Tags();
+ this.offset = o.unmarshal(this.buf, this.offset, this.i);
+ return o;
+ } catch (BufferUnderflowException e) {
+ }
+ }
+ // not enough data
+
+ if (this.i <= this.offset) {
+ this.offset = 0;
+ this.i = 0;
+ } else if (i == buf.length) {
+ byte[] src = this.buf;
+ // TODO: better size estimation
+ if (offset == 0) this.buf = new byte[Math.min(Tags.colferSizeMax, this.buf.length * 4)];
+ System.arraycopy(src, this.offset, this.buf, 0, this.i - this.offset);
+ this.i -= this.offset;
+ this.offset = 0;
+ }
+ assert this.i < this.buf.length;
+
+ int n = in.read(buf, i, buf.length - i);
+ if (n < 0) {
+ if (this.i > this.offset)
+ throw new InputMismatchException("colfer: pending data with EOF");
+ return null;
+ }
+ assert n > 0;
+ i += n;
+ }
+ }
+
+ }
+
+
+ /**
+ * Serializes the object.
+ * All {@code null} elements in {@link #strings} will be replaced with a {@code new} value.
+ * All {@code null} elements in {@link #longs} will be replaced with a {@code new} value.
+ * All {@code null} elements in {@link #booleans} will be replaced with a {@code new} value.
+ * @param out the data destination.
+ * @param buf the initial buffer or {@code null}.
+ * @return the final buffer. When the serial fits into {@code buf} then the return is {@code buf}.
+ * Otherwise the return is a new buffer, large enough to hold the whole serial.
+ * @throws IOException from {@code out}.
+ * @throws IllegalStateException on an upper limit breach defined by either {@link #colferSizeMax} or {@link #colferListMax}.
+ */
+ public byte[] marshal(OutputStream out, byte[] buf) throws IOException {
+ // TODO: better size estimation
+ if (buf == null || buf.length == 0)
+ buf = new byte[Math.min(Tags.colferSizeMax, 2048)];
+
+ while (true) {
+ int i;
+ try {
+ i = marshal(buf, 0);
+ } catch (BufferOverflowException e) {
+ buf = new byte[Math.min(Tags.colferSizeMax, buf.length * 4)];
+ continue;
+ }
+
+ out.write(buf, 0, i);
+ return buf;
+ }
+ }
+
+ /**
+ * Serializes the object.
+ * All {@code null} elements in {@link #strings} will be replaced with a {@code new} value.
+ * All {@code null} elements in {@link #longs} will be replaced with a {@code new} value.
+ * All {@code null} elements in {@link #booleans} will be replaced with a {@code new} value.
+ * @param buf the data destination.
+ * @param offset the initial index for {@code buf}, inclusive.
+ * @return the final index for {@code buf}, exclusive.
+ * @throws BufferOverflowException when {@code buf} is too small.
+ * @throws IllegalStateException on an upper limit breach defined by either {@link #colferSizeMax} or {@link #colferListMax}.
+ */
+ public int marshal(byte[] buf, int offset) {
+ int i = offset;
+
+ try {
+ if (this.strings.length != 0) {
+ buf[i++] = (byte) 0;
+ StringTag[] a = this.strings;
+
+ int x = a.length;
+ if (x > Tags.colferListMax)
+ throw new IllegalStateException(format("colfer: kamon/context/generated/binary/context.Tags.strings length %d exceeds %d elements", x, Tags.colferListMax));
+ while (x > 0x7f) {
+ buf[i++] = (byte) (x | 0x80);
+ x >>>= 7;
+ }
+ buf[i++] = (byte) x;
+
+ for (int ai = 0; ai < a.length; ai++) {
+ StringTag o = a[ai];
+ if (o == null) {
+ o = new StringTag();
+ a[ai] = o;
+ }
+ i = o.marshal(buf, i);
+ }
+ }
+
+ if (this.longs.length != 0) {
+ buf[i++] = (byte) 1;
+ LongTag[] a = this.longs;
+
+ int x = a.length;
+ if (x > Tags.colferListMax)
+ throw new IllegalStateException(format("colfer: kamon/context/generated/binary/context.Tags.longs length %d exceeds %d elements", x, Tags.colferListMax));
+ while (x > 0x7f) {
+ buf[i++] = (byte) (x | 0x80);
+ x >>>= 7;
+ }
+ buf[i++] = (byte) x;
+
+ for (int ai = 0; ai < a.length; ai++) {
+ LongTag o = a[ai];
+ if (o == null) {
+ o = new LongTag();
+ a[ai] = o;
+ }
+ i = o.marshal(buf, i);
+ }
+ }
+
+ if (this.booleans.length != 0) {
+ buf[i++] = (byte) 2;
+ BooleanTag[] a = this.booleans;
+
+ int x = a.length;
+ if (x > Tags.colferListMax)
+ throw new IllegalStateException(format("colfer: kamon/context/generated/binary/context.Tags.booleans length %d exceeds %d elements", x, Tags.colferListMax));
+ while (x > 0x7f) {
+ buf[i++] = (byte) (x | 0x80);
+ x >>>= 7;
+ }
+ buf[i++] = (byte) x;
+
+ for (int ai = 0; ai < a.length; ai++) {
+ BooleanTag o = a[ai];
+ if (o == null) {
+ o = new BooleanTag();
+ a[ai] = o;
+ }
+ i = o.marshal(buf, i);
+ }
+ }
+
+ buf[i++] = (byte) 0x7f;
+ return i;
+ } catch (ArrayIndexOutOfBoundsException e) {
+ if (i - offset > Tags.colferSizeMax)
+ throw new IllegalStateException(format("colfer: kamon/context/generated/binary/context.Tags exceeds %d bytes", Tags.colferSizeMax));
+ if (i > buf.length) throw new BufferOverflowException();
+ throw e;
+ }
+ }
+
+ /**
+ * Deserializes the object.
+ * @param buf the data source.
+ * @param offset the initial index for {@code buf}, inclusive.
+ * @return the final index for {@code buf}, exclusive.
+ * @throws BufferUnderflowException when {@code buf} is incomplete. (EOF)
+ * @throws SecurityException on an upper limit breach defined by either {@link #colferSizeMax} or {@link #colferListMax}.
+ * @throws InputMismatchException when the data does not match this object's schema.
+ */
+ public int unmarshal(byte[] buf, int offset) {
+ return unmarshal(buf, offset, buf.length);
+ }
+
+ /**
+ * Deserializes the object.
+ * @param buf the data source.
+ * @param offset the initial index for {@code buf}, inclusive.
+ * @param end the index limit for {@code buf}, exclusive.
+ * @return the final index for {@code buf}, exclusive.
+ * @throws BufferUnderflowException when {@code buf} is incomplete. (EOF)
+ * @throws SecurityException on an upper limit breach defined by either {@link #colferSizeMax} or {@link #colferListMax}.
+ * @throws InputMismatchException when the data does not match this object's schema.
+ */
+ public int unmarshal(byte[] buf, int offset, int end) {
+ if (end > buf.length) end = buf.length;
+ int i = offset;
+
+ try {
+ byte header = buf[i++];
+
+ if (header == (byte) 0) {
+ int length = 0;
+ for (int shift = 0; true; shift += 7) {
+ byte b = buf[i++];
+ length |= (b & 0x7f) << shift;
+ if (shift == 28 || b >= 0) break;
+ }
+ if (length < 0 || length > Tags.colferListMax)
+ throw new SecurityException(format("colfer: kamon/context/generated/binary/context.Tags.strings length %d exceeds %d elements", length, Tags.colferListMax));
+
+ StringTag[] a = new StringTag[length];
+ for (int ai = 0; ai < length; ai++) {
+ StringTag o = new StringTag();
+ i = o.unmarshal(buf, i, end);
+ a[ai] = o;
+ }
+ this.strings = a;
+ header = buf[i++];
+ }
+
+ if (header == (byte) 1) {
+ int length = 0;
+ for (int shift = 0; true; shift += 7) {
+ byte b = buf[i++];
+ length |= (b & 0x7f) << shift;
+ if (shift == 28 || b >= 0) break;
+ }
+ if (length < 0 || length > Tags.colferListMax)
+ throw new SecurityException(format("colfer: kamon/context/generated/binary/context.Tags.longs length %d exceeds %d elements", length, Tags.colferListMax));
+
+ LongTag[] a = new LongTag[length];
+ for (int ai = 0; ai < length; ai++) {
+ LongTag o = new LongTag();
+ i = o.unmarshal(buf, i, end);
+ a[ai] = o;
+ }
+ this.longs = a;
+ header = buf[i++];
+ }
+
+ if (header == (byte) 2) {
+ int length = 0;
+ for (int shift = 0; true; shift += 7) {
+ byte b = buf[i++];
+ length |= (b & 0x7f) << shift;
+ if (shift == 28 || b >= 0) break;
+ }
+ if (length < 0 || length > Tags.colferListMax)
+ throw new SecurityException(format("colfer: kamon/context/generated/binary/context.Tags.booleans length %d exceeds %d elements", length, Tags.colferListMax));
+
+ BooleanTag[] a = new BooleanTag[length];
+ for (int ai = 0; ai < length; ai++) {
+ BooleanTag o = new BooleanTag();
+ i = o.unmarshal(buf, i, end);
+ a[ai] = o;
+ }
+ this.booleans = a;
+ header = buf[i++];
+ }
+
+ if (header != (byte) 0x7f)
+ throw new InputMismatchException(format("colfer: unknown header at byte %d", i - 1));
+ } finally {
+ if (i > end && end - offset < Tags.colferSizeMax) throw new BufferUnderflowException();
+ if (i < 0 || i - offset > Tags.colferSizeMax)
+ throw new SecurityException(format("colfer: kamon/context/generated/binary/context.Tags exceeds %d bytes", Tags.colferSizeMax));
+ if (i > end) throw new BufferUnderflowException();
+ }
+
+ return i;
+ }
+
+ // {@link Serializable} version number.
+ private static final long serialVersionUID = 3L;
+
+ // {@link Serializable} Colfer extension.
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ // TODO: better size estimation
+ byte[] buf = new byte[1024];
+ int n;
+ while (true) try {
+ n = marshal(buf, 0);
+ break;
+ } catch (BufferUnderflowException e) {
+ buf = new byte[4 * buf.length];
+ }
+
+ out.writeInt(n);
+ out.write(buf, 0, n);
+ }
+
+ // {@link Serializable} Colfer extension.
+ private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException {
+ init();
+
+ int n = in.readInt();
+ byte[] buf = new byte[n];
+ in.readFully(buf);
+ unmarshal(buf, 0);
+ }
+
+ // {@link Serializable} Colfer extension.
+ private void readObjectNoData() throws ObjectStreamException {
+ init();
+ }
+
+ /**
+ * Gets kamon/context/generated/binary/context.Tags.strings.
+ * @return the value.
+ */
+ public StringTag[] getStrings() {
+ return this.strings;
+ }
+
+ /**
+ * Sets kamon/context/generated/binary/context.Tags.strings.
+ * @param value the replacement.
+ */
+ public void setStrings(StringTag[] value) {
+ this.strings = value;
+ }
+
+ /**
+ * Sets kamon/context/generated/binary/context.Tags.strings.
+ * @param value the replacement.
+ * @return {link this}.
+ */
+ public Tags withStrings(StringTag[] value) {
+ this.strings = value;
+ return this;
+ }
+
+ /**
+ * Gets kamon/context/generated/binary/context.Tags.longs.
+ * @return the value.
+ */
+ public LongTag[] getLongs() {
+ return this.longs;
+ }
+
+ /**
+ * Sets kamon/context/generated/binary/context.Tags.longs.
+ * @param value the replacement.
+ */
+ public void setLongs(LongTag[] value) {
+ this.longs = value;
+ }
+
+ /**
+ * Sets kamon/context/generated/binary/context.Tags.longs.
+ * @param value the replacement.
+ * @return {link this}.
+ */
+ public Tags withLongs(LongTag[] value) {
+ this.longs = value;
+ return this;
+ }
+
+ /**
+ * Gets kamon/context/generated/binary/context.Tags.booleans.
+ * @return the value.
+ */
+ public BooleanTag[] getBooleans() {
+ return this.booleans;
+ }
+
+ /**
+ * Sets kamon/context/generated/binary/context.Tags.booleans.
+ * @param value the replacement.
+ */
+ public void setBooleans(BooleanTag[] value) {
+ this.booleans = value;
+ }
+
+ /**
+ * Sets kamon/context/generated/binary/context.Tags.booleans.
+ * @param value the replacement.
+ * @return {link this}.
+ */
+ public Tags withBooleans(BooleanTag[] value) {
+ this.booleans = value;
+ return this;
+ }
+
+ @Override
+ public final int hashCode() {
+ int h = 1;
+ for (StringTag o : this.strings) h = 31 * h + (o == null ? 0 : o.hashCode());
+ for (LongTag o : this.longs) h = 31 * h + (o == null ? 0 : o.hashCode());
+ for (BooleanTag o : this.booleans) h = 31 * h + (o == null ? 0 : o.hashCode());
+ return h;
+ }
+
+ @Override
+ public final boolean equals(Object o) {
+ return o instanceof Tags && equals((Tags) o);
+ }
+
+ public final boolean equals(Tags o) {
+ if (o == null) return false;
+ if (o == this) return true;
+ return o.getClass() == Tags.class
+ && java.util.Arrays.equals(this.strings, o.strings)
+ && java.util.Arrays.equals(this.longs, o.longs)
+ && java.util.Arrays.equals(this.booleans, o.booleans);
+ }
+
+}
diff --git a/kamon-core/src/main/scala/kamon/context/BinaryPropagation.scala b/kamon-core/src/main/scala/kamon/context/BinaryPropagation.scala
index 75e65c44..296003d5 100644
--- a/kamon-core/src/main/scala/kamon/context/BinaryPropagation.scala
+++ b/kamon-core/src/main/scala/kamon/context/BinaryPropagation.scala
@@ -20,7 +20,9 @@ import java.io.{ByteArrayInputStream, ByteArrayOutputStream, InputStream, Output
import com.typesafe.config.Config
import kamon.context.BinaryPropagation.{ByteStreamReader, ByteStreamWriter}
-import kamon.context.generated.binary.context.{Context => ColferContext, Entry => ColferEntry}
+import kamon.context.generated.binary.context.{Context => ColferContext, Entry => ColferEntry, Tags => ColferTags}
+import kamon.context.generated.binary.context.{StringTag => ColferStringTag, LongTag => ColferLongTag, BooleanTag => ColferBooleanTag}
+import kamon.tag.{Tag, TagSet}
import org.slf4j.LoggerFactory
import scala.reflect.ClassTag
@@ -152,7 +154,7 @@ object BinaryPropagation {
* configured entry readers and writers.
*/
class Default(settings: Settings) extends Propagation[ByteStreamReader, ByteStreamWriter] {
- private val _log = LoggerFactory.getLogger(classOf[BinaryPropagation.Default])
+ private val _logger = LoggerFactory.getLogger(classOf[BinaryPropagation.Default])
private val _streamPool = new ThreadLocal[Default.ReusableByteStreamWriter] {
override def initialValue(): Default.ReusableByteStreamWriter = new Default.ReusableByteStreamWriter(128)
}
@@ -170,39 +172,29 @@ object BinaryPropagation {
}
contextData.failed.foreach {
- case NonFatal(t) => _log.warn("Failed to read Context from ByteStreamReader", t)
+ case NonFatal(t) => _logger.warn("Failed to read Context from ByteStreamReader", t)
}
contextData.map { colferContext =>
// Context tags
- var tagSectionsCount = colferContext.tags.length
- if (tagSectionsCount > 0 && tagSectionsCount % 2 != 0) {
- _log.warn("Malformed Context tags found, tags consistency might be compromised")
- tagSectionsCount -= 1
+ val tagsBuilder = Map.newBuilder[String, Any]
+ if(colferContext.tags != null) {
+ colferContext.tags.strings.foreach(t => tagsBuilder += (t.key -> t.value))
+ colferContext.tags.longs.foreach(t => tagsBuilder += (t.key -> t.value))
+ colferContext.tags.booleans.foreach(t => tagsBuilder += (t.key -> t.value))
}
-
- val tags = if (tagSectionsCount > 0) {
- val tagsBuilder = Map.newBuilder[String, String]
- var tagIndex = 0
- while (tagIndex < tagSectionsCount) {
- tagsBuilder += (colferContext.tags(tagIndex) -> colferContext.tags(tagIndex + 1))
- tagIndex += 2
- }
- tagsBuilder.result()
-
- } else Map.empty[String, String]
-
+ val tags = TagSet.from(tagsBuilder.result())
// Only reads the entries for which there is a registered reader
colferContext.entries.foldLeft(Context.of(tags)) {
case (context, entryData) =>
- settings.incomingEntries.get(entryData.name).map { entryReader =>
+ settings.incomingEntries.get(entryData.key).map { entryReader =>
var contextWithEntry = context
try {
- contextWithEntry = entryReader.read(ByteStreamReader.of(entryData.content), context)
+ contextWithEntry = entryReader.read(ByteStreamReader.of(entryData.value), context)
} catch {
- case NonFatal(t) => _log.warn("Failed to read entry [{}]", entryData.name.asInstanceOf[Any], t)
+ case NonFatal(t) => _logger.warn("Failed to read entry [{}]", entryData.key.asInstanceOf[Any], t)
}
contextWithEntry
@@ -218,17 +210,36 @@ object BinaryPropagation {
val output = _streamPool.get()
val contextOutgoingBuffer = _contextBufferPool.get()
- if (context.tags.nonEmpty) {
- val tags = Array.ofDim[String](context.tags.size * 2)
- var tagIndex = 0
- context.tags.foreach {
- case (key, value) =>
- tags.update(tagIndex, key)
- tags.update(tagIndex + 1, value)
- tagIndex += 2
+ if(context.tags.nonEmpty()) {
+ val tagsData = new ColferTags()
+ val strings = Array.newBuilder[ColferStringTag]
+ val longs = Array.newBuilder[ColferLongTag]
+ val booleans = Array.newBuilder[ColferBooleanTag]
+
+ context.tags.iterator().foreach {
+ case t: Tag.String =>
+ val st = new ColferStringTag()
+ st.setKey(t.key)
+ st.setValue(t.value)
+ strings += st
+
+ case t: Tag.Long =>
+ val lt = new ColferLongTag()
+ lt.setKey(t.key)
+ lt.setValue(t.value)
+ longs += lt
+
+ case t: Tag.Boolean =>
+ val bt = new ColferBooleanTag()
+ bt.setKey(t.key)
+ bt.setValue(t.value)
+ booleans += bt
}
- contextData.tags = tags
+ tagsData.setStrings(strings.result())
+ tagsData.setLongs(longs.result())
+ tagsData.setBooleans(booleans.result())
+ contextData.setTags(tagsData)
}
if (context.entries.nonEmpty) {
@@ -239,10 +250,10 @@ object BinaryPropagation {
output.reset()
entryWriter.write(context, output)
- colferEntry.name = entryName
- colferEntry.content = output.toByteArray()
+ colferEntry.key = entryName
+ colferEntry.value = output.toByteArray()
} catch {
- case NonFatal(t) => _log.warn("Failed to write entry [{}]", entryName.asInstanceOf[Any], t)
+ case NonFatal(t) => _logger.warn("Failed to write entry [{}]", entryName.asInstanceOf[Any], t)
}
colferEntry
@@ -255,7 +266,7 @@ object BinaryPropagation {
val contextSize = contextData.marshal(contextOutgoingBuffer, 0)
writer.write(contextOutgoingBuffer, 0, contextSize)
} catch {
- case NonFatal(t) => _log.warn("Failed to write Context to ByteStreamWriter", t)
+ case NonFatal(t) => _logger.warn("Failed to write Context to ByteStreamWriter", t)
}
}
}
diff --git a/kamon-core/src/main/scala/kamon/context/Context.scala b/kamon-core/src/main/scala/kamon/context/Context.scala
index 2a7a382e..054a7897 100644
--- a/kamon-core/src/main/scala/kamon/context/Context.scala
+++ b/kamon-core/src/main/scala/kamon/context/Context.scala
@@ -16,32 +16,92 @@
package kamon
package context
-import java.util.{Map => JavaMap}
-import scala.collection.JavaConverters._
-
-class Context private (val entries: Map[String, Any], val tags: Map[String, String]) {
+import kamon.tag.TagSet
+
+
+/**
+ * An immutable set of information that is tied to the processing of single operation in a service. A Context instance
+ * can contain tags and entries.
+ *
+ * Context tags are built on top of the TagSet abstraction that ships with Kamon and since Kamon knows exactly what
+ * types of values can be included in a TagSet it can automatically serialize and deserialize them when going over
+ * HTTP and/or Binary transports.
+ *
+ * Context entries can contain any arbitrary type specified by the user, but require additional configuration and
+ * implementation of entry readers and writers if you need them to go over HTTP and/or Binary transports.
+ *
+ * Context instances are meant to be constructed by using the builder functions on the Context companion object.
+ *
+ */
+class Context private (val entries: Map[String, Any], val tags: TagSet) {
+ /**
+ * Gets an entry from this Context. If the entry is present it's current value is returned, otherwise the empty value
+ * from the provided key will be returned.
+ */
def get[T](key: Context.Key[T]): T =
entries.getOrElse(key.name, key.emptyValue).asInstanceOf[T]
- def getTag(tagKey: String): Option[String] =
- tags.get(tagKey)
+ /**
+ * Executes a lookup on the context tags. The actual return type depends on the provided lookup instance. Take a look
+ * at the built-in lookups available on the Lookups companion object.
+ */
+ def getTag[T](lookup: TagSet.Lookup[T]): T =
+ tags.get(lookup)
+
+
+ /**
+ * Creates a new Context instance that includes the provided key and value. If the provided key was already
+ * associated with another value then the previous value will be discarded and overwritten with the provided one.
+ */
def withKey[T](key: Context.Key[T], value: T): Context =
new Context(entries.updated(key.name, value), tags)
- def withTag(tagKey: String, tagValue: String): Context =
- new Context(entries, tags.updated(tagKey, tagValue))
- def withTags(tags: Map[String, String]): Context =
- new Context(entries, this.tags ++ tags)
+ /**
+ * Creates a new Context instance that includes the provided tag key and value. If the provided tag key was already
+ * associated with another value then the previous tag value will be discarded and overwritten with the provided one.
+ */
+ def withTag(key: String, value: String): Context =
+ new Context(entries, tags.withTag(key, value))
+
+
+ /**
+ * Creates a new Context instance that includes the provided tag key and value. If the provided tag key was already
+ * associated with another value then the previous tag value will be discarded and overwritten with the provided one.
+ */
+ def withTag(key: String, value: Long): Context =
+ new Context(entries, tags.withTag(key, value))
+
+
+ /**
+ * Creates a new Context instance that includes the provided tag key and value. If the provided tag key was already
+ * associated with another value then the previous tag value will be discarded and overwritten with the provided one.
+ */
+ def withTag(key: String, value: Boolean): Context =
+ new Context(entries, tags.withTag(key, value))
+
+
+ /**
+ * Creates a new Context instance that includes the provided tags. If any of the tags in this instance are associated
+ * to a key present on the provided tags then the previous values will be discarded and overwritten with the provided
+ * ones.
+ */
+ def withTags(tags: TagSet): Context =
+ new Context(entries, this.tags.and(tags))
- def withTags(tags: JavaMap[String, String]): Context =
- new Context(entries, this.tags ++ tags.asScala.toMap)
+ /**
+ * Returns whether this Context does not have any tags and does not have any entries.
+ */
def isEmpty(): Boolean =
entries.isEmpty && tags.isEmpty
+
+ /**
+ * Returns whether this Context has any information, either as tags or entries.
+ */
def nonEmpty(): Boolean =
!isEmpty()
@@ -49,32 +109,48 @@ class Context private (val entries: Map[String, Any], val tags: Map[String, Stri
object Context {
- val Empty = new Context(Map.empty, Map.empty)
+ val Empty = new Context(Map.empty, TagSet.Empty)
- def of(tags: JavaMap[String, String]): Context =
- new Context(Map.empty, tags.asScala.toMap)
-
- def of(tags: Map[String, String]): Context =
+ /**
+ * Creates a new Context instance with the provided tags and no entries.
+ */
+ def of(tags: TagSet): Context =
new Context(Map.empty, tags)
+
+ /**
+ * Creates a new Context instance with the provided key and no tags.
+ */
def of[T](key: Context.Key[T], value: T): Context =
- new Context(Map(key.name -> value), Map.empty)
+ new Context(Map(key.name -> value), TagSet.Empty)
- def of[T](key: Context.Key[T], value: T, tags: JavaMap[String, String]): Context =
- new Context(Map(key.name -> value), tags.asScala.toMap)
- def of[T](key: Context.Key[T], value: T, tags: Map[String, String]): Context =
+ /**
+ * Creates a new Context instance with a single entry and the provided tags.
+ */
+ def of[T](key: Context.Key[T], value: T, tags: TagSet): Context =
new Context(Map(key.name -> value), tags)
+
+ /**
+ * Creates a new Context instance with two entries and no tags.
+ */
def of[T, U](keyOne: Context.Key[T], valueOne: T, keyTwo: Context.Key[U], valueTwo: U): Context =
- new Context(Map(keyOne.name -> valueOne, keyTwo.name -> valueTwo), Map.empty)
+ new Context(Map(keyOne.name -> valueOne, keyTwo.name -> valueTwo), TagSet.Empty)
- def of[T, U](keyOne: Context.Key[T], valueOne: T, keyTwo: Context.Key[U], valueTwo: U, tags: JavaMap[String, String]): Context =
- new Context(Map(keyOne.name -> valueOne, keyTwo.name -> valueTwo), tags.asScala.toMap)
- def of[T, U](keyOne: Context.Key[T], valueOne: T, keyTwo: Context.Key[U], valueTwo: U, tags: Map[String, String]): Context =
+ /**
+ * Creates a new Context instance with two entries and the provided tags.
+ */
+ def of[T, U](keyOne: Context.Key[T], valueOne: T, keyTwo: Context.Key[U], valueTwo: U, tags: TagSet): Context =
new Context(Map(keyOne.name -> valueOne, keyTwo.name -> valueTwo), tags)
+
+ /**
+ * Creates a new Context.Key instance that can be used to insert and retrieve values from the context entries.
+ * Context keys must have a unique name since they will be looked up in transports by their name and the context
+ * entries are internally stored using their key name as index.
+ */
def key[T](name: String, emptyValue: T): Context.Key[T] =
new Context.Key(name, emptyValue)
diff --git a/kamon-core/src/main/scala/kamon/context/HttpPropagation.scala b/kamon-core/src/main/scala/kamon/context/HttpPropagation.scala
index 6a15e2a6..fbee75cc 100644
--- a/kamon-core/src/main/scala/kamon/context/HttpPropagation.scala
+++ b/kamon-core/src/main/scala/kamon/context/HttpPropagation.scala
@@ -17,6 +17,7 @@ package kamon
package context
import com.typesafe.config.Config
+import kamon.tag.{Tag, TagSet}
import org.slf4j.LoggerFactory
import scala.reflect.ClassTag
@@ -91,7 +92,7 @@ object HttpPropagation {
* 3. Read all context entries using the incoming entries configuration.
*/
override def read(reader: HeaderReader): Context = {
- val tags = Map.newBuilder[String, String]
+ val tags = Map.newBuilder[String, Any]
// Tags encoded together in the context tags header.
try {
@@ -99,7 +100,7 @@ object HttpPropagation {
contextTagsHeader.split(";").foreach(tagData => {
val tagPair = tagData.split("=")
if (tagPair.length == 2) {
- tags += (tagPair(0) -> tagPair(1))
+ tags += (tagPair(0) -> parseTagValue(tagPair(1)))
}
})
}
@@ -118,7 +119,7 @@ object HttpPropagation {
}
// Incoming Entries
- settings.incomingEntries.foldLeft(Context.of(tags.result())) {
+ settings.incomingEntries.foldLeft(Context.of(TagSet.from(tags.result()))) {
case (context, (entryName, entryDecoder)) =>
var result = context
try {
@@ -145,10 +146,12 @@ object HttpPropagation {
}
// Write tags with specific mappings or append them to the context tags header.
- context.tags.foreach {
- case (tagKey, tagValue) => settings.tagsMappings.get(tagKey) match {
- case Some(mappedHeader) => writer.write(mappedHeader, tagValue)
- case None => appendTag(tagKey, tagValue)
+ context.tags.iterator().foreach { tag =>
+ val tagKey = tag.key
+
+ settings.tagsMappings.get(tagKey) match {
+ case Some(mappedHeader) => writer.write(mappedHeader, tagValueWithPrefix(tag))
+ case None => appendTag(tagKey, Tag.unwrapValue(tag).toString)
}
}
@@ -167,6 +170,54 @@ object HttpPropagation {
}
}
}
+
+
+ private val _longTypePrefix = "l:"
+ private val _booleanTypePrefix = "b:"
+ private val _booleanTrue = "true"
+ private val _booleanFalse = "false"
+
+ /**
+ * Tries to infer and parse a value into one of the supported tag types: String, Long or Boolean by looking for the
+ * type indicator prefix on the tag value. If the inference fails it will default to treat the value as a String.
+ */
+ private def parseTagValue(value: String): Any = {
+ if (value.isEmpty || value.length < 2) // Empty and short values definitely do not have type indicators.
+ value
+ else {
+ if(value.startsWith(_longTypePrefix)) {
+ // Try to parse the content as a Long value.
+ val remaining = value.substring(2)
+ try {
+ java.lang.Long.parseLong(remaining)
+ } catch {
+ case _: Throwable => remaining
+ }
+
+ } else if(value.startsWith(_booleanTypePrefix)) {
+
+ // Try to parse the content as a Boolean value.
+ val remaining = value.substring(2)
+ if(remaining.equals(_booleanTrue))
+ true
+ else if(remaining.equals(_booleanFalse))
+ false
+ else
+ remaining
+
+ } else value
+ }
+ }
+
+ /**
+ * Returns the actual value to be written in the HTTP transport, with a type prefix if applicable.
+ */
+ private def tagValueWithPrefix(tag: Tag): String = tag match {
+ case t: Tag.String => t.value
+ case t: Tag.Boolean => _booleanTypePrefix + t.value.toString
+ case t: Tag.Long => _longTypePrefix + t.value.toString
+ }
+
}
case class Settings(
diff --git a/kamon-core/src/main/scala/kamon/instrumentation/HttpServer.scala b/kamon-core/src/main/scala/kamon/instrumentation/HttpServer.scala
index 659da8aa..68975711 100644
--- a/kamon-core/src/main/scala/kamon/instrumentation/HttpServer.scala
+++ b/kamon-core/src/main/scala/kamon/instrumentation/HttpServer.scala
@@ -6,7 +6,9 @@ import java.time.Duration
import com.typesafe.config.Config
import kamon.context.Context
import kamon.instrumentation.HttpServer.Settings.TagMode
-import kamon.metric.MeasurementUnit.{time, information}
+import kamon.metric.MeasurementUnit.{information, time}
+import kamon.tag.TagSet
+import kamon.tag.Lookups.{any, option}
import kamon.trace.{IdentityProvider, Span}
import kamon.util.GlobPathFilter
import org.slf4j.LoggerFactory
@@ -348,7 +350,7 @@ object HttpServer {
span.disableMetrics()
- for { traceIdTag <- settings.traceIDTag; customTraceID <- context.getTag(traceIdTag) } {
+ for {traceIdTag <- settings.traceIDTagLookup; customTraceID <- context.getTag(traceIdTag) } {
val identifier = Kamon.identityProvider.traceIdGenerator().from(customTraceID)
if(identifier != IdentityProvider.NoIdentifier)
span.withTraceID(identifier)
@@ -361,9 +363,12 @@ object HttpServer {
}
addRequestTag("http.url", request.url, settings.urlTagMode)
- addRequestTag("http.method", request.method, settings.urlTagMode)
+ addRequestTag("http.method", request.method, settings.methodTagMode)
settings.contextTags.foreach {
- case (tagName, mode) => context.getTag(tagName).foreach(tagValue => addRequestTag(tagName, tagValue, mode))
+ case (tagName, mode) =>
+ val tagValue = context.getTag(any(tagName))
+ if(tagValue != null)
+ addRequestTag(tagName, tagValue.toString, mode)
}
span.start()
@@ -385,7 +390,7 @@ object HttpServer {
propagationChannel: String,
enableServerMetrics: Boolean,
enableTracing: Boolean,
- traceIDTag: Option[String],
+ traceIDTagLookup: Option[TagSet.Lookup[Option[String]]],
enableSpanMetrics: Boolean,
urlTagMode: TagMode,
methodTagMode: TagMode,
@@ -424,7 +429,10 @@ object HttpServer {
// Tracing settings
val enableTracing = config.getBoolean("tracing.enabled")
- val traceIdTag = Option(config.getString("tracing.preferred-trace-id-tag")).filterNot(_ == "none")
+ val traceIdTagLookup = Option(config.getString("tracing.preferred-trace-id-tag"))
+ .filterNot(_ == "none")
+ .map(option)
+
val enableSpanMetrics = config.getBoolean("tracing.span-metrics")
val urlTagMode = TagMode.from(config.getString("tracing.tags.url"))
val methodTagMode = TagMode.from(config.getString("tracing.tags.method"))
@@ -441,12 +449,12 @@ object HttpServer {
case (pattern, operationName) => (new GlobPathFilter(pattern), operationName)
}
- Settings(
+ Settings (
enablePropagation,
propagationChannel,
enableServerMetrics,
enableTracing,
- traceIdTag,
+ traceIdTagLookup,
enableSpanMetrics,
urlTagMode,
methodTagMode,
diff --git a/kamon-core/src/main/scala/kamon/metric/Accumulator.scala b/kamon-core/src/main/scala/kamon/metric/Accumulator.scala
index bf412980..945feeeb 100644
--- a/kamon-core/src/main/scala/kamon/metric/Accumulator.scala
+++ b/kamon-core/src/main/scala/kamon/metric/Accumulator.scala
@@ -17,7 +17,7 @@ package kamon.metric
import java.time.{Duration, Instant}
-import kamon.{Kamon, Tags}
+import kamon.{Kamon, STags}
import kamon.metric.PeriodSnapshotAccumulator.{MetricDistributionKey, MetricValueKey}
import kamon.util.Clock
@@ -169,6 +169,6 @@ class PeriodSnapshotAccumulator(duration: Duration, margin: Duration) {
}
object PeriodSnapshotAccumulator {
- case class MetricValueKey(name: String, tags: Tags, unit: MeasurementUnit)
- case class MetricDistributionKey(name: String, tags: Tags, unit: MeasurementUnit, dynamicRange: DynamicRange)
+ case class MetricValueKey(name: String, tags: STags, unit: MeasurementUnit)
+ case class MetricDistributionKey(name: String, tags: STags, unit: MeasurementUnit, dynamicRange: DynamicRange)
}
diff --git a/kamon-core/src/main/scala/kamon/metric/Metric.scala b/kamon-core/src/main/scala/kamon/metric/Metric.scala
index f5ce7b45..69ef88bc 100644
--- a/kamon-core/src/main/scala/kamon/metric/Metric.scala
+++ b/kamon-core/src/main/scala/kamon/metric/Metric.scala
@@ -34,12 +34,12 @@ trait Metric[T] {
def unit: MeasurementUnit
def refine(tags: JTags): T
- def refine(tags: Tags): T
+ def refine(tags: STags): T
def refine(tags: (String, String)*): T
def refine(tag: String, value: String): T
def remove(tags: JTags): Boolean
- def remove(tags: Tags): Boolean
+ def remove(tags: STags): Boolean
def remove(tags: (String, String)*): Boolean
def remove(tag: String, value: String): Boolean
}
@@ -52,7 +52,7 @@ trait CounterMetric extends Metric[Counter] with Counter
private[kamon] abstract sealed class BaseMetric[T, S](val instrumentType: InstrumentType) extends Metric[T] {
- private[kamon] val instruments = TrieMap.empty[Tags, T]
+ private[kamon] val instruments = TrieMap.empty[STags, T]
protected lazy val baseInstrument: T = instruments.atomicGetOrElseUpdate(Map.empty, createInstrument(Map.empty))
override def refine(tags: JTags):T =
@@ -72,7 +72,7 @@ private[kamon] abstract sealed class BaseMetric[T, S](val instrumentType: Instru
override def remove(tags: JTags):Boolean =
remove(tags.asScala.toMap)
- override def remove(tags: Tags): Boolean =
+ override def remove(tags: STags): Boolean =
if(tags.nonEmpty) instruments.remove(tags).nonEmpty else false
override def remove(tags: (String, String)*): Boolean =
@@ -88,7 +88,7 @@ private[kamon] abstract sealed class BaseMetric[T, S](val instrumentType: Instru
private[kamon] def incarnations(): Seq[Map[String, String]] =
instruments.keys.toSeq
- protected def createInstrument(tags: Tags): T
+ protected def createInstrument(tags: STags): T
protected def createSnapshot(instrument: T): S
}
@@ -106,7 +106,7 @@ private[kamon] final class HistogramMetricImpl(val name: String, val unit: Measu
override def record(value: Long, times: Long): Unit =
baseInstrument.record(value, times)
- override protected def createInstrument(tags: Tags): Histogram =
+ override protected def createInstrument(tags: STags): Histogram =
factory.get().buildHistogram(customDynamicRange)(name, tags, unit)
override protected def createSnapshot(instrument: Histogram): MetricDistribution =
@@ -118,7 +118,7 @@ private[kamon] final class RangeSamplerMetricImpl(val name: String, val unit: Me
extends BaseMetric[RangeSampler, MetricDistribution](RangeSampler) with RangeSamplerMetric {
private val logger = LoggerFactory.getLogger(classOf[RangeSamplerMetric])
- private val scheduledSamplers = TrieMap.empty[Tags, ScheduledFuture[_]]
+ private val scheduledSamplers = TrieMap.empty[STags, ScheduledFuture[_]]
def dynamicRange: DynamicRange =
baseInstrument.dynamicRange
@@ -141,7 +141,7 @@ private[kamon] final class RangeSamplerMetricImpl(val name: String, val unit: Me
override def sample(): Unit =
baseInstrument.sample()
- override protected def createInstrument(tags: Tags): RangeSampler = {
+ override protected def createInstrument(tags: STags): RangeSampler = {
val rangeSampler = factory.get().buildRangeSampler(customDynamicRange, customSampleInterval)(name, tags, unit)
val sampleInterval = rangeSampler.sampleInterval.toMillis
val scheduledFuture = scheduler.scheduleAtFixedRate(scheduledSampler(rangeSampler), sampleInterval, sampleInterval, TimeUnit.MILLISECONDS)
@@ -153,7 +153,7 @@ private[kamon] final class RangeSamplerMetricImpl(val name: String, val unit: Me
override def remove(tags: JTags): Boolean =
removeAndStopSampler(tags.asScala.toMap)
- override def remove(tags: Tags): Boolean =
+ override def remove(tags: STags): Boolean =
removeAndStopSampler(tags)
override def remove(tags: (String, String)*): Boolean =
@@ -162,7 +162,7 @@ private[kamon] final class RangeSamplerMetricImpl(val name: String, val unit: Me
override def remove(tag: String, value: String): Boolean =
removeAndStopSampler(Map(tag -> value))
- private def removeAndStopSampler(tags: Tags): Boolean = {
+ private def removeAndStopSampler(tags: STags): Boolean = {
val removed = super.remove(tags)
if(removed)
scheduledSamplers.remove(tags).foreach(sf => {
@@ -190,7 +190,7 @@ private[kamon] final class CounterMetricImpl(val name: String, val unit: Measure
override def increment(times: Long): Unit =
baseInstrument.increment(times)
- override protected def createInstrument(tags: Tags): Counter =
+ override protected def createInstrument(tags: STags): Counter =
factory.get().buildCounter(name, tags, unit)
override protected def createSnapshot(instrument: Counter): MetricValue =
@@ -215,7 +215,7 @@ private[kamon] final class GaugeMetricImpl(val name: String, val unit: Measureme
override def set(value: Long): Unit =
baseInstrument.set(value)
- override protected def createInstrument(tags: Tags): Gauge =
+ override protected def createInstrument(tags: STags): Gauge =
factory.get().buildGauge(name, tags, unit)
override protected def createSnapshot(instrument: Gauge): MetricValue =
diff --git a/kamon-core/src/main/scala/kamon/metric/PeriodSnapshot.scala b/kamon-core/src/main/scala/kamon/metric/PeriodSnapshot.scala
index 50a5f778..09a0e029 100644
--- a/kamon-core/src/main/scala/kamon/metric/PeriodSnapshot.scala
+++ b/kamon-core/src/main/scala/kamon/metric/PeriodSnapshot.scala
@@ -39,13 +39,13 @@ case class MetricsSnapshot(
* Snapshot for instruments that internally track a single value. Meant to be used for counters and gauges.
*
*/
-case class MetricValue(name: String, tags: Tags, unit: MeasurementUnit, value: Long)
+case class MetricValue(name: String, tags: STags, unit: MeasurementUnit, value: Long)
/**
* Snapshot for instruments that internally the distribution of values in a defined dynamic range. Meant to be used
* with histograms and min max counters.
*/
-case class MetricDistribution(name: String, tags: Tags, unit: MeasurementUnit, dynamicRange: DynamicRange, distribution: Distribution)
+case class MetricDistribution(name: String, tags: STags, unit: MeasurementUnit, dynamicRange: DynamicRange, distribution: Distribution)
trait Distribution {
diff --git a/kamon-core/src/main/scala/kamon/metric/Timer.scala b/kamon-core/src/main/scala/kamon/metric/Timer.scala
index 74d203a9..749ac876 100644
--- a/kamon-core/src/main/scala/kamon/metric/Timer.scala
+++ b/kamon-core/src/main/scala/kamon/metric/Timer.scala
@@ -15,7 +15,7 @@
package kamon.metric
-import kamon.{JTags, Tags}
+import kamon.{JTags, STags}
trait Timer extends Histogram {
def start(): StartedTimer
@@ -82,7 +82,7 @@ private[kamon] final class TimerMetricImpl(val underlyingHistogram: HistogramMet
override def refine(tags: JTags): Timer =
refine(tags.asScala.toMap)
- override def refine(tags: Tags): Timer =
+ override def refine(tags: STags): Timer =
new TimerImpl(underlyingHistogram.refine(tags))
override def refine(tags: (String, String)*): Timer =
@@ -94,7 +94,7 @@ private[kamon] final class TimerMetricImpl(val underlyingHistogram: HistogramMet
override def remove(tags: JTags): Boolean =
remove(tags.asScala.toMap)
- override def remove(tags: Tags): Boolean =
+ override def remove(tags: STags): Boolean =
underlyingHistogram.remove(tags)
override def remove(tags: (String, String)*): Boolean =
diff --git a/kamon-core/src/main/scala/kamon/package.scala b/kamon-core/src/main/scala/kamon/package.scala
index d694206c..3da676cd 100644
--- a/kamon-core/src/main/scala/kamon/package.scala
+++ b/kamon-core/src/main/scala/kamon/package.scala
@@ -23,7 +23,7 @@ import scala.collection.concurrent.TrieMap
package object kamon {
- type Tags = Map[String, String]
+ type STags = Map[String, String]
type JTags = java.util.Map[String, String]
diff --git a/kamon-core/src/main/scala/kamon/tag/Lookups.scala b/kamon-core/src/main/scala/kamon/tag/Lookups.scala
new file mode 100644
index 00000000..9390f472
--- /dev/null
+++ b/kamon-core/src/main/scala/kamon/tag/Lookups.scala
@@ -0,0 +1,159 @@
+package kamon.tag
+
+import java.util.Optional
+import java.lang.{Boolean => JBoolean, Long => JLong, String => JString}
+
+import kamon.tag.TagSet.Lookup
+
+import scala.reflect.ClassTag
+
+object Lookups {
+
+ /**
+ * Finds a value associated to the provided key and returns it. If the key is not present then a null is returned.
+ */
+ def any(key: JString) = new Lookup[Any] {
+ override def execute(storage: TagSet.Storage): Any =
+ findAndTransform(key, storage, _any, null)
+ }
+
+
+ /**
+ * Finds a String value associated to the provided key and returns it. If the key is not present or the value
+ * associated with they is not a String then a null is returned.
+ */
+ def plain(key: JString) = new Lookup[JString] {
+ override def execute(storage: TagSet.Storage): JString =
+ findAndTransform(key, storage, _plainString, null)
+ }
+
+
+ /**
+ * Finds a String value associated to the provided key and returns it, wrapped in an Option[String]. If the key is
+ * not present or the value associated with they is not a String then a None is returned.
+ */
+ def option(key: JString) = new Lookup[Option[JString]] {
+ override def execute(storage: TagSet.Storage): Option[JString] =
+ findAndTransform(key, storage, _stringOption, None)
+ }
+
+
+ /**
+ * Finds a String value associated to the provided key and returns it, wrapped in an Optional[String]. If the key
+ * is not present or the value associated with they is not a String then Optional.empty() is returned.
+ */
+ def optional(key: JString) = new Lookup[Optional[String]] {
+ override def execute(storage: TagSet.Storage): Optional[String] =
+ findAndTransform(key, storage, _stringOptional, Optional.empty())
+ }
+
+
+ /**
+ * Finds the value associated to the provided key and coerces it to a String representation. If the key is not
+ * present then "unknown" (as a String) will be returned. If the value associated with the key is not a String then
+ * the value of the key will be transformed into a String and returned.
+ *
+ * This lookup type is guaranteed to return a non-null String representation of value.
+ */
+ def coerce(key: String) = new Lookup[String] {
+ override def execute(storage: TagSet.Storage): String = {
+ val value = storage.get(key)
+ if(value == null)
+ "unknown"
+ else
+ value.toString
+ }
+ }
+
+
+ /**
+ * Finds a Boolean value associated to the provided key and returns it. If the key is not present or the value
+ * associated with they is not a Boolean then a null is returned.
+ */
+ def plainBoolean(key: String) = new Lookup[JBoolean] {
+ override def execute(storage: TagSet.Storage): JBoolean =
+ findAndTransform(key, storage, _plainBoolean, null)
+ }
+
+
+ /**
+ * Finds a Boolean value associated to the provided key and returns it, wrapped in an Option[Boolean]. If the key
+ * is not present or the value associated with they is not a Boolean then a None is returned.
+ */
+ def booleanOption(key: String) = new Lookup[Option[JBoolean]] {
+ override def execute(storage: TagSet.Storage): Option[JBoolean] =
+ findAndTransform(key, storage, _booleanOption, None)
+ }
+
+
+ /**
+ * Finds a Boolean value associated to the provided key and returns it, wrapped in an Optional[Boolean]. If the key
+ * is not present or the value associated with they is not a Boolean then Optional.empty() is returned.
+ */
+ def booleanOptional(key: String) = new Lookup[Optional[JBoolean]] {
+ override def execute(storage: TagSet.Storage): Optional[JBoolean] =
+ findAndTransform(key, storage, _booleanOptional, Optional.empty())
+ }
+
+
+ /**
+ * Finds a Long value associated to the provided key and returns it. If the key is not present or the value
+ * associated with they is not a Long then a null is returned.
+ */
+ def plainLong(key: String) = new Lookup[JLong] {
+ override def execute(storage: TagSet.Storage): JLong =
+ findAndTransform(key, storage, _plainLong, null)
+ }
+
+
+ /**
+ * Finds a Long value associated to the provided key and returns it, wrapped in an Option[Long]. If the key is
+ * not present or the value associated with they is not a Long then a None is returned.
+ */
+ def longOption(key: String) = new Lookup[Option[JLong]] {
+ override def execute(storage: TagSet.Storage): Option[JLong] =
+ findAndTransform(key, storage, _longOption, None)
+ }
+
+
+ /**
+ * Finds a Long value associated to the provided key and returns it, wrapped in an Optional[Long]. If the key
+ * is not present or the value associated with they is not a Long then Optional.empty() is returned.
+ */
+ def longOptional(key: String) = new Lookup[Optional[JLong]] {
+ override def execute(storage: TagSet.Storage): Optional[JLong] =
+ findAndTransform(key, storage, _longOptional, Optional.empty())
+ }
+
+
+ ////////////////////////////////////////////////////////////////
+ // Transformation helpers for the lookup DSL //
+ ////////////////////////////////////////////////////////////////
+ @inline
+ private def findAndTransform[T, R](key: String, storage: TagSet.Storage, transform: R => T, default: T)
+ (implicit ct: ClassTag[R]): T = {
+
+ // This assumes that this code will only be used to lookup values from a Tags instance
+ // for which the underlying map always has "null" as the default value.
+ val value = storage.get(key)
+
+ if(value == null || !ct.runtimeClass.isInstance(value))
+ default
+ else
+ transform(value.asInstanceOf[R])
+ }
+
+ private val _any = (a: Any) => a
+ private val _plainString = (a: JString) => a
+ private val _stringOption = (a: JString) => Option(a)
+ private val _stringOptional = (a: JString) => Optional.of(a)
+
+ private val _plainLong = (a: JLong) => a
+ private val _longOption = (a: JLong) => Option(a)
+ private val _longOptional = (a: JLong) => Optional.of(a)
+
+ private val _plainBoolean = (a: JBoolean) => a
+ private val _booleanOption = (a: JBoolean) => Option(a)
+ private val _booleanOptional = (a: JBoolean) => Optional.of(a)
+
+}
diff --git a/kamon-core/src/main/scala/kamon/tag/Tag.scala b/kamon-core/src/main/scala/kamon/tag/Tag.scala
new file mode 100644
index 00000000..69a5d7e7
--- /dev/null
+++ b/kamon-core/src/main/scala/kamon/tag/Tag.scala
@@ -0,0 +1,50 @@
+package kamon.tag
+
+import java.lang.{Boolean => JBoolean, Long => JLong, String => JString}
+
+/**
+ * Marker trait for allowed Tag implementations. Users are not meant to create implementations of this trait outside
+ * of Kamon. Furthermore, users of TagSet might never need to interact with these classes but rather perform lookups
+ * using the lookup DSL.
+ */
+sealed trait Tag {
+ def key: JString
+}
+
+object Tag {
+
+ /**
+ * Represents a String key pointing to a String value.
+ */
+ trait String extends Tag {
+ def value: JString
+ }
+
+
+ /**
+ * Represents a String key pointing to a Boolean value.
+ */
+ trait Boolean extends Tag {
+ def value: JBoolean
+ }
+
+
+ /**
+ * Represents a String key pointing to a Long value.
+ */
+ trait Long extends Tag {
+ def value: JLong
+ }
+
+
+ /**
+ * Returns the value held inside of a Tag instance. This utility function is specially useful when iterating over
+ * tags but not caring about the concrete tag type.
+ */
+ def unwrapValue(tag: Tag): Any = tag match {
+ case t: Tag.String => t.value
+ case t: Tag.Boolean => t.value
+ case t: Tag.Long => t.value
+ }
+}
+
diff --git a/kamon-core/src/main/scala/kamon/tag/TagSet.scala b/kamon-core/src/main/scala/kamon/tag/TagSet.scala
new file mode 100644
index 00000000..c304a9df
--- /dev/null
+++ b/kamon-core/src/main/scala/kamon/tag/TagSet.scala
@@ -0,0 +1,427 @@
+package kamon.tag
+
+import kamon.tag.TagSet.Lookup
+
+import java.lang.{Boolean => JBoolean, Long => JLong, String => JString}
+import java.util.function.BiConsumer
+
+import org.eclipse.collections.impl.map.mutable.UnifiedMap
+import org.slf4j.LoggerFactory
+
+/**
+ * A immutable collection of key/value pairs with specialized support for storing String keys pointing to String, Long
+ * and/or Boolean values.
+ *
+ * Instances of Tags store all pairs in the same data structure, but preserving type information for the stored pairs
+ * and providing a simple DSL for accessing those values and expressing type expectations. It is also possible to
+ * lookup pairs without prescribing a mechanism for handling missing values. I.e. users of this class can decide
+ * whether to receive a null, java.util.Optional, scala.Option or any other value when looking up a pair.
+ *
+ * TagSet instances can only be created from the builder functions on the TagSet companion object. There are two
+ * different options to read the contained pairs from a Tags instance:
+ *
+ * 1. Using the lookup DSL. You can use the Lookup DSL when you know exactly that you are trying to get out of the
+ * tags instance. The lookup DSL is biased towards String keys since they are by far the most common case. For
+ * example, to get a given tag as an Option[String] and another as an Option[Boolean] the following code should
+ * suffice:
+ *
+ * import kamon.tag.Tags.Lookup._
+ * val tags = Tags.from(tagMap)
+ * val name = tags.get(option("name"))
+ * val isSignedIn = tags.get(booleanOption("isSignedIn"))
+ *
+ * 2. Using the .all() and .iterator variants. This option requires you to test the returned instances to verify
+ * whether they are a Tag.String, Tag.Long or Tag.Boolean instance and act accordingly. Fortunately this
+ * cumbersome operation is rarely necessary on user-facing code.
+ *
+ */
+class TagSet private(private val _underlying: UnifiedMap[String, Any]) {
+ import TagSet.withPair
+
+
+ /**
+ * Creates a new TagSet instance that includes the provided key/value pair. If the provided key was already associated
+ * with another value then the previous value will be discarded and overwritten with the provided one.
+ */
+ def withTag(key: String, value: JString): TagSet =
+ withPair(this, key, value)
+
+
+ /**
+ * Creates a new TagSet instance that includes the provided key/value pair. If the provided key was already associated
+ * with another value then the previous value will be discarded and overwritten with the provided one.
+ */
+ def withTag(key: String, value: JBoolean): TagSet =
+ withPair(this, key, value)
+
+
+ /**
+ * Creates a new TagSet instance that includes the provided key/value pair. If the provided key was already associated
+ * with another value then the previous value will be discarded and overwritten with the provided one.
+ */
+ def withTag(key: String, value: JLong): TagSet =
+ withPair(this, key, value)
+
+
+ /**
+ * Creates a new TagSet instance that includes all the tags from the provided Tags instance. If any of the tags in this
+ * instance are associated to a key present on the provided instance then the previous value will be discarded and
+ * overwritten with the provided one.
+ */
+ def withTags(other: TagSet): TagSet =
+ and(other)
+
+
+ /**
+ * Creates a new TagSet instance that includes the provided key/value pair. If the provided key was already associated
+ * with another value then the previous value will be discarded and overwritten with the provided one.
+ */
+ def and(key: String, value: JString): TagSet =
+ withPair(this, key, value)
+
+
+ /**
+ * Creates a new TagSet instance that includes the provided key/value pair. If the provided key was already associated
+ * with another value then the previous value will be discarded and overwritten with the provided one.
+ */
+ def and(key: String, value: JBoolean): TagSet =
+ withPair(this, key, value)
+
+
+ /**
+ * Creates a new TagSet instance that includes the provided key/value pair. If the provided key was already associated
+ * with another value then the previous value will be discarded and overwritten with the provided one.
+ */
+ def and(key: String, value: JLong): TagSet =
+ withPair(this, key, value)
+
+
+ /**
+ * Creates a new TagSet instance that includes all the tags from the provided Tags instance. If any of the tags in this
+ * instance are associated to a key present on the provided instance then the previous value will be discarded and
+ * overwritten with the provided one.
+ */
+ def and(other: TagSet): TagSet = {
+ val mergedMap = new UnifiedMap[String, Any](other._underlying.size() + this._underlying.size())
+ mergedMap.putAll(this._underlying)
+ mergedMap.putAll(other._underlying)
+ new TagSet(mergedMap)
+ }
+
+
+ /**
+ * Returns whether this TagSet instance does not contain any tags.
+ */
+ def isEmpty(): Boolean =
+ _underlying.isEmpty
+
+
+ /**
+ * Returns whether this TagSet instance contains any tags.
+ */
+ def nonEmpty(): Boolean =
+ !_underlying.isEmpty
+
+
+ /**
+ * Executes a tag lookup. The return type of this function will depend on the provided Lookup. Take a look at the
+ * built-in lookups on the [[Lookups]] companion object for more information.
+ */
+ def get[T](lookup: Lookup[T]): T =
+ lookup.execute(_storage)
+
+
+ /**
+ * Returns a immutable sequence of tags created from the contained tags internal representation. Calling this method
+ * will cause the creation of a new data structure. Unless you really need to have all the tags as immutable
+ * instances it is recommended to use the .iterator() function instead.
+ *
+ * The returned sequence contains immutable values and is safe to share across threads.
+ */
+ def all(): Seq[Tag] = {
+ var tags: List[Tag] = Nil
+
+ _underlying.forEach(new BiConsumer[String, Any] {
+ override def accept(key: String, value: Any): Unit = value match {
+ case v: String => tags = new TagSet.immutable.String(key, v) :: tags
+ case v: Boolean => tags = new TagSet.immutable.Boolean(key, v) :: tags
+ case v: Long => tags = new TagSet.immutable.Long(key, v) :: tags
+ }
+ })
+
+ tags
+ }
+
+
+ /**
+ * Returns an iterator of tags. The underlying iterator reuses the Tag instances to avoid unnecessary intermediate
+ * allocations and thus, it is not safe to share across threads. The most common case for tags iterators is on
+ * reporters which will need to iterate through all existent tags only to copy their values into a separate data
+ * structure that will be sent to the external systems.
+ */
+ def iterator(): Iterator[Tag] = new Iterator[Tag] {
+ private val _entriesIterator = _underlying.keyValuesView().iterator()
+ private var _longTag: TagSet.mutable.Long = null
+ private var _stringTag: TagSet.mutable.String = null
+ private var _booleanTag: TagSet.mutable.Boolean = null
+
+ override def hasNext: Boolean =
+ _entriesIterator.hasNext
+
+ override def next(): Tag = {
+ val pair = _entriesIterator.next()
+ pair.getTwo match {
+ case v: String => stringTag(pair.getOne, v)
+ case v: Boolean => booleanTag(pair.getOne, v)
+ case v: Long => longTag(pair.getOne, v)
+ }
+ }
+
+ private def stringTag(key: JString, value: JString): Tag.String =
+ if(_stringTag == null) {
+ _stringTag = new TagSet.mutable.String(key, value)
+ _stringTag
+ } else _stringTag.updated(key, value)
+
+ private def booleanTag(key: JString, value: JBoolean): Tag.Boolean =
+ if(_booleanTag == null) {
+ _booleanTag = new TagSet.mutable.Boolean(key, value)
+ _booleanTag
+ } else _booleanTag.updated(key, value)
+
+ private def longTag(key: JString, value: JLong): Tag.Long =
+ if(_longTag == null) {
+ _longTag = new TagSet.mutable.Long(key, value)
+ _longTag
+ } else _longTag.updated(key, value)
+ }
+
+ override def equals(other: Any): Boolean =
+ other != null && other.isInstanceOf[TagSet] && other.asInstanceOf[TagSet]._underlying == _underlying
+
+ override def hashCode(): Int =
+ _underlying.hashCode()
+
+ override def toString: JString = {
+ val sb = new StringBuilder()
+ sb.append("Tags{")
+
+ var hasTags = false
+ val iterator = _underlying.keyValuesView().iterator()
+ while(iterator.hasNext) {
+ val pair = iterator.next()
+ if(hasTags)
+ sb.append(",")
+
+ sb.append(pair.getOne)
+ .append("=")
+ .append(pair.getTwo)
+
+ hasTags = true
+ }
+
+ sb.append("}").toString()
+ }
+
+ private val _storage = new TagSet.Storage {
+ override def get(key: String): Any = _underlying.get(key)
+ override def iterator(): Iterator[Tag] = TagSet.this.iterator()
+ override def isEmpty(): Boolean = _underlying.isEmpty
+ }
+}
+
+object TagSet {
+
+ /**
+ * Describes a strategy to lookup values from a TagSet instance. Implementations of this interface will be provided
+ * with the actual data structure containing the tags and must perform any necessary runtime type checks to ensure
+ * that the returned value is in assignable to the expected type T.
+ *
+ * Several implementation are provided in the Lookup companion object and it is recommended to import and use those
+ * definitions when looking up keys from a Tags instance.
+ */
+ trait Lookup[T] {
+
+ /**
+ * Tries to find a value on a TagSet.Storage and returns a representation of it. In some cases the stored object
+ * might be returned as-is, in some others it might be transformed or wrapped on Option/Optional to handle missing
+ * values. Take a look at the Lookups companion object for examples..
+ */
+ def execute(storage: TagSet.Storage): T
+ }
+
+
+ /**
+ * A temporary structure that accumulates key/value and creates a new TagSet instance from them. It is faster to use
+ * a Builder and add tags to it rather than creating TagSet and add each key individually. Builder instances rely on
+ * internal mutable state and are not thread safe.
+ */
+ trait Builder {
+
+ /** Adds a new key/value pair to the builder. */
+ def add(key: String, value: Any): Builder
+
+ /** Creates a new TagSet instance that includes all valid key/value pairs added to this builder. */
+ def create(): TagSet
+ }
+
+
+ /**
+ * Abstracts the actual storage used for a TagSet. This interface resembles a stripped down interface of an immutable
+ * map of String to Any, used to expose the underlying structure where tags are stored to Lookups, without leaking
+ * the actual implementation.
+ */
+ trait Storage {
+
+ /**
+ * Gets the value associated with the provided key, or null if no value was found. The decision of returning null
+ * when the key is not present is a conscious one, backed by the fact that users will never be exposed to this
+ * storage interface and they can decide their way of handling missing values by selecting an appropriate lookup.
+ */
+ def get(key: String): Any
+
+ /**
+ * Provides an Iterator that can go through all key/value pairs contained in the Storage instance.
+ */
+ def iterator(): Iterator[Tag]
+
+ /**
+ * Returns true if there are no tags in the storage.
+ */
+ def isEmpty(): Boolean
+
+ }
+
+
+ /**
+ * A valid instance of tags that doesn't contain any pairs.
+ */
+ val Empty = new TagSet(UnifiedMap.newMap[String, Any]())
+
+
+ /**
+ * Creates a new Builder instance.
+ */
+ def builder(): Builder = new Builder {
+ private var _tagCount = 0
+ private var _tags: List[(String, Any)] = Nil
+
+ override def add(key: String, value: Any): Builder = {
+ if(isValidPair(key, value)) {
+ _tagCount += 1
+ _tags = (key -> value) :: _tags
+ }
+
+ this
+ }
+
+ override def create(): TagSet = {
+ val newMap = new UnifiedMap[String, Any](_tagCount)
+ _tags.foreach { case (key, value) => newMap.put(key, value) }
+ new TagSet(newMap)
+ }
+ }
+
+
+ /**
+ * Construct a new TagSet instance with a single key/value pair.
+ */
+ def from(key: String, value: JString): TagSet =
+ withPair(Empty, key, value)
+
+
+ /**
+ * Construct a new TagSet instance with a single key/value pair.
+ */
+ def from(key: String, value: JBoolean): TagSet =
+ withPair(Empty, key, value)
+
+
+ /**
+ * Construct a new TagSet instance with a single key/value pair.
+ */
+ def from(key: String, value: JLong): TagSet =
+ withPair(Empty, key, value)
+
+
+ /**
+ * Constructs a new TagSet instance from a Map. The returned TagSet will only contain the entries that have String,
+ * Long or Boolean values from the supplied map, any other entry in the map will be ignored.
+ */
+ def from(map: Map[String, Any]): TagSet = {
+ val unifiedMap = new UnifiedMap[String, Any](map.size)
+ map.foreach { pair => if(isValidPair(pair._1, pair._2)) unifiedMap.put(pair._1, pair._2)}
+
+ new TagSet(unifiedMap)
+ }
+
+
+ /**
+ * Constructs a new TagSet instance from a Map. The returned TagSet will only contain the entries that have String,
+ * Long or Boolean values from the supplied map, any other entry in the map will be ignored.
+ */
+ def from(map: java.util.Map[String, Any]): TagSet = {
+ val unifiedMap = new UnifiedMap[String, Any](map.size)
+ map.forEach(new BiConsumer[String, Any] {
+ override def accept(key: String, value: Any): Unit =
+ if(isValidPair(key, value)) unifiedMap.put(key, value)
+ })
+
+ new TagSet(unifiedMap)
+ }
+
+
+ private val _logger = LoggerFactory.getLogger(classOf[TagSet])
+
+ private def withPair(parent: TagSet, key: String, value: Any): TagSet =
+ if(isValidPair(key, value)) {
+ val mergedMap = new UnifiedMap[String, Any](parent._underlying.size() + 1)
+ mergedMap.putAll(parent._underlying)
+ mergedMap.put(key, value)
+ new TagSet(mergedMap)
+ } else
+ parent
+
+ private def isValidPair(key: String, value: Any): Boolean = {
+ val isValidKey = key != null && key.nonEmpty
+ val isValidValue = isAllowedTagValue(value)
+ val isValid = isValidKey && isValidValue
+
+ if(!isValid && _logger.isDebugEnabled) {
+ if(!isValidKey && !isValidValue)
+ _logger.debug(s"Dismissing tag with invalid key [$key] and invalid value [$value]")
+ else if(!isValidKey)
+ _logger.debug(s"Dismissing tag with invalid key [$key] and value [$value]")
+ else
+ _logger.debug(s"Dismissing tag with key [$key] and invalid value [$value]")
+ }
+
+ isValid
+ }
+
+ private def isAllowedTagValue(v: Any): Boolean =
+ v != null && (v.isInstanceOf[String] || v.isInstanceOf[Boolean] || v.isInstanceOf[Long])
+
+ private object immutable {
+ case class String(key: JString, value: JString) extends Tag.String
+ case class Boolean(key: JString, value: JBoolean) extends Tag.Boolean
+ case class Long(key: JString, value: JLong) extends Tag.Long
+ }
+
+ private object mutable {
+ case class String(var key: JString, var value: JString) extends Tag.String with Updateable[JString]
+ case class Boolean(var key: JString, var value: JBoolean) extends Tag.Boolean with Updateable[JBoolean]
+ case class Long(var key: JString, var value: JLong) extends Tag.Long with Updateable[JLong]
+
+ trait Updateable[T] {
+ var key: JString
+ var value: T
+
+ def updated(key: JString, value: T): this.type = {
+ this.key = key
+ this.value = value
+ this
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/kamon-status-page/src/main/vue/dist/favicon/android-chrome-96x96.png b/kamon-status-page/src/main/vue/dist/favicon/android-chrome-96x96.png~HEAD
index 76bf6c17..76bf6c17 100644
--- a/kamon-status-page/src/main/vue/dist/favicon/android-chrome-96x96.png
+++ b/kamon-status-page/src/main/vue/dist/favicon/android-chrome-96x96.png~HEAD
Binary files differ
diff --git a/kamon-status-page/src/main/vue/dist/favicon/android-chrome-96x96.png~separate the status-page project from the status API b/kamon-status-page/src/main/vue/dist/favicon/android-chrome-96x96.png~separate the status-page project from the status API
new file mode 100644
index 00000000..76bf6c17
--- /dev/null
+++ b/kamon-status-page/src/main/vue/dist/favicon/android-chrome-96x96.png~separate the status-page project from the status API
Binary files differ
diff --git a/kamon-status-page/src/main/vue/dist/favicon/apple-touch-icon.png b/kamon-status-page/src/main/vue/dist/favicon/apple-touch-icon.png~HEAD
index 590c4c95..590c4c95 100644
--- a/kamon-status-page/src/main/vue/dist/favicon/apple-touch-icon.png
+++ b/kamon-status-page/src/main/vue/dist/favicon/apple-touch-icon.png~HEAD
Binary files differ
diff --git a/kamon-status-page/src/main/vue/dist/favicon/apple-touch-icon.png~separate the status-page project from the status API b/kamon-status-page/src/main/vue/dist/favicon/apple-touch-icon.png~separate the status-page project from the status API
new file mode 100644
index 00000000..590c4c95
--- /dev/null
+++ b/kamon-status-page/src/main/vue/dist/favicon/apple-touch-icon.png~separate the status-page project from the status API
Binary files differ
diff --git a/kamon-status-page/src/main/vue/dist/favicon/browserconfig.xml b/kamon-status-page/src/main/vue/dist/favicon/browserconfig.xml~HEAD
index 7738dd46..7738dd46 100644
--- a/kamon-status-page/src/main/vue/dist/favicon/browserconfig.xml
+++ b/kamon-status-page/src/main/vue/dist/favicon/browserconfig.xml~HEAD
diff --git a/kamon-status-page/src/main/vue/dist/favicon/browserconfig.xml~separate the status-page project from the status API b/kamon-status-page/src/main/vue/dist/favicon/browserconfig.xml~separate the status-page project from the status API
new file mode 100644
index 00000000..7738dd46
--- /dev/null
+++ b/kamon-status-page/src/main/vue/dist/favicon/browserconfig.xml~separate the status-page project from the status API
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<browserconfig>
+ <msapplication>
+ <tile>
+ <square150x150logo src="/assets/favicon/mstile-150x150.png"/>
+ <TileColor>#ffffff</TileColor>
+ </tile>
+ </msapplication>
+</browserconfig>
diff --git a/kamon-status-page/src/main/vue/dist/favicon/favicon-16x16.png b/kamon-status-page/src/main/vue/dist/favicon/favicon-16x16.png~HEAD
index 2058b6c0..2058b6c0 100644
--- a/kamon-status-page/src/main/vue/dist/favicon/favicon-16x16.png
+++ b/kamon-status-page/src/main/vue/dist/favicon/favicon-16x16.png~HEAD
Binary files differ
diff --git a/kamon-status-page/src/main/vue/dist/favicon/favicon-16x16.png~separate the status-page project from the status API b/kamon-status-page/src/main/vue/dist/favicon/favicon-16x16.png~separate the status-page project from the status API
new file mode 100644
index 00000000..2058b6c0
--- /dev/null
+++ b/kamon-status-page/src/main/vue/dist/favicon/favicon-16x16.png~separate the status-page project from the status API
Binary files differ
diff --git a/kamon-status-page/src/main/vue/dist/favicon/favicon-32x32.png b/kamon-status-page/src/main/vue/dist/favicon/favicon-32x32.png~HEAD
index dd4acaea..dd4acaea 100644
--- a/kamon-status-page/src/main/vue/dist/favicon/favicon-32x32.png
+++ b/kamon-status-page/src/main/vue/dist/favicon/favicon-32x32.png~HEAD
Binary files differ
diff --git a/kamon-status-page/src/main/vue/dist/favicon/favicon-32x32.png~separate the status-page project from the status API b/kamon-status-page/src/main/vue/dist/favicon/favicon-32x32.png~separate the status-page project from the status API
new file mode 100644
index 00000000..dd4acaea
--- /dev/null
+++ b/kamon-status-page/src/main/vue/dist/favicon/favicon-32x32.png~separate the status-page project from the status API
Binary files differ
diff --git a/kamon-status-page/src/main/vue/dist/favicon/favicon.ico b/kamon-status-page/src/main/vue/dist/favicon/favicon.ico~HEAD
index 6affcb49..6affcb49 100644
--- a/kamon-status-page/src/main/vue/dist/favicon/favicon.ico
+++ b/kamon-status-page/src/main/vue/dist/favicon/favicon.ico~HEAD
Binary files differ
diff --git a/kamon-status-page/src/main/vue/dist/favicon/favicon.ico~separate the status-page project from the status API b/kamon-status-page/src/main/vue/dist/favicon/favicon.ico~separate the status-page project from the status API
new file mode 100644
index 00000000..6affcb49
--- /dev/null
+++ b/kamon-status-page/src/main/vue/dist/favicon/favicon.ico~separate the status-page project from the status API
Binary files differ
diff --git a/kamon-status-page/src/main/vue/dist/favicon/mstile-150x150.png b/kamon-status-page/src/main/vue/dist/favicon/mstile-150x150.png~HEAD
index 74b62d82..74b62d82 100644
--- a/kamon-status-page/src/main/vue/dist/favicon/mstile-150x150.png
+++ b/kamon-status-page/src/main/vue/dist/favicon/mstile-150x150.png~HEAD
Binary files differ
diff --git a/kamon-status-page/src/main/vue/dist/favicon/mstile-150x150.png~separate the status-page project from the status API b/kamon-status-page/src/main/vue/dist/favicon/mstile-150x150.png~separate the status-page project from the status API
new file mode 100644
index 00000000..74b62d82
--- /dev/null
+++ b/kamon-status-page/src/main/vue/dist/favicon/mstile-150x150.png~separate the status-page project from the status API
Binary files differ
diff --git a/kamon-status-page/src/main/vue/dist/favicon/safari-pinned-tab.svg b/kamon-status-page/src/main/vue/dist/favicon/safari-pinned-tab.svg~HEAD
index f031b484..f031b484 100644
--- a/kamon-status-page/src/main/vue/dist/favicon/safari-pinned-tab.svg
+++ b/kamon-status-page/src/main/vue/dist/favicon/safari-pinned-tab.svg~HEAD
diff --git a/kamon-status-page/src/main/vue/dist/favicon/safari-pinned-tab.svg~separate the status-page project from the status API b/kamon-status-page/src/main/vue/dist/favicon/safari-pinned-tab.svg~separate the status-page project from the status API
new file mode 100644
index 00000000..f031b484
--- /dev/null
+++ b/kamon-status-page/src/main/vue/dist/favicon/safari-pinned-tab.svg~separate the status-page project from the status API
@@ -0,0 +1,23 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
+ "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
+ width="107.000000pt" height="107.000000pt" viewBox="0 0 107.000000 107.000000"
+ preserveAspectRatio="xMidYMid meet">
+<metadata>
+Created by potrace 1.11, written by Peter Selinger 2001-2013
+</metadata>
+<g transform="translate(0.000000,107.000000) scale(0.100000,-0.100000)"
+fill="#000000" stroke="none">
+<path d="M140 1052 c-76 -34 -101 -96 -129 -317 -11 -85 -6 -405 5 -414 5 -3
+10 -28 12 -56 8 -159 89 -256 200 -242 61 8 125 35 116 49 -3 5 -2 8 3 7 25
+-4 41 12 123 124 49 67 133 182 187 255 54 74 97 138 95 143 -1 5 2 9 7 9 9 0
+82 95 102 132 7 13 15 25 19 28 4 3 34 43 68 90 34 47 67 92 75 100 8 8 22 30
+33 48 16 29 17 34 4 42 -8 5 -84 9 -169 9 -140 0 -158 -2 -193 -21 -21 -12
+-38 -28 -38 -35 0 -7 -3 -13 -8 -13 -4 0 -26 -24 -49 -52 -63 -81 -76 -96
+-108 -132 l-30 -34 -94 125 c-51 69 -108 133 -125 144 -31 19 -77 24 -106 11z"/>
+<path d="M647 310 c-48 -66 -87 -126 -87 -132 0 -7 33 -45 72 -85 l72 -73 174
+0 c144 0 174 2 178 15 5 12 -30 63 -91 131 -5 6 -55 68 -110 137 -55 69 -105
+126 -110 127 -6 0 -50 -54 -98 -120z"/>
+</g>
+</svg>
diff --git a/kamon-status-page/src/main/vue/dist/favicon/site.webmanifest b/kamon-status-page/src/main/vue/dist/favicon/site.webmanifest~HEAD
index 2f99d71e..2f99d71e 100644
--- a/kamon-status-page/src/main/vue/dist/favicon/site.webmanifest
+++ b/kamon-status-page/src/main/vue/dist/favicon/site.webmanifest~HEAD
diff --git a/kamon-status-page/src/main/vue/dist/favicon/site.webmanifest~separate the status-page project from the status API b/kamon-status-page/src/main/vue/dist/favicon/site.webmanifest~separate the status-page project from the status API
new file mode 100644
index 00000000..2f99d71e
--- /dev/null
+++ b/kamon-status-page/src/main/vue/dist/favicon/site.webmanifest~separate the status-page project from the status API
@@ -0,0 +1,14 @@
+{
+ "name": "Kamon",
+ "short_name": "Kamon",
+ "icons": [
+ {
+ "src": "/assets/favicon/android-chrome-96x96.png",
+ "sizes": "96x96",
+ "type": "image/png"
+ }
+ ],
+ "theme_color": "#ffffff",
+ "background_color": "#ffffff",
+ "display": "standalone"
+}
diff --git a/kamon-status-page/src/main/vue/dist/img/logo.592500c9.svg b/kamon-status-page/src/main/vue/dist/img/logo.592500c9.svg~HEAD
index d351c48a..d351c48a 100644
--- a/kamon-status-page/src/main/vue/dist/img/logo.592500c9.svg
+++ b/kamon-status-page/src/main/vue/dist/img/logo.592500c9.svg~HEAD
diff --git a/kamon-status-page/src/main/vue/dist/img/logo.592500c9.svg~separate the status-page project from the status API b/kamon-status-page/src/main/vue/dist/img/logo.592500c9.svg~separate the status-page project from the status API
new file mode 100644
index 00000000..d351c48a
--- /dev/null
+++ b/kamon-status-page/src/main/vue/dist/img/logo.592500c9.svg~separate the status-page project from the status API
@@ -0,0 +1,26 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="Layer_1" width="1529.7" height="283.96">
+ <g id="XMLID_903_-5" fill="#616161" transform="matrix(.99987 0 0 .99987 .19 0)">
+ <path id="XMLID_914_-7" d="M388.3 82.3v62.3l34.9-40h34l-38.9 41.7 39.3 66.7h-34l-20-37c-3.6-6.4-7.2-9.6-10.8-9.6-3 .6-4.5 2.6-4.5 5.5v41H360V68.4h14.7c7.5 0 13.6 6.4 13.6 13.9z" class="st0"/>
+ <path id="XMLID_911_-4" d="M492.9 129.9h-28.3c3.4-20.8 18.1-31 44-31 31 0 46.8 10.2 47.4 31v38.3c0 31-18.7 44.8-49.1 47-27 2.1-45.7-10.4-45.7-35.3.6-27 20.2-34.2 48.7-37 12.1-1.5 18.3-5.1 18.3-11.3-.6-6.4-6.6-9.6-18.3-9.6-10 0-15.5 2.5-17 7.9zm35.7 36.9v-10.4c-6.6 2.8-14.2 5.1-22.5 6.8-11.3 2.1-17 7.4-17 15.7.6 8.9 5.3 13.2 14.2 13.2 15.7 0 25.3-9.3 25.3-25.3z" class="st0"/>
+ <path id="XMLID_909_-1" d="M598.3 141.1V213h-28.7v-66.1c0-32.1 16.2-48 48.2-48 14.5 0 25.5 3.2 33.6 9.6 8.1-6.2 19.1-9.1 33.6-9.1 32.1 0 48 15.9 47.8 48v66.1h-14.7c-9.3-.6-14-5.1-14-14v-57.8c-.6-11.7-7-17.4-19.6-17.4-12.5 0-18.9 5.7-19.1 17.4V213H637v-71.8c-.6-11.7-7-17.4-19.6-17.4-12.6 0-18.9 5.6-19.1 17.3z" class="st0"/>
+ <path id="XMLID_906_-8" d="M850.6 157.3c0 38.7-17.2 58.2-51.2 58.2s-51-19.6-51-58.2c0-39.1 17-58.4 51-58.4s51.2 19.3 51.2 58.4zm-73.6 0c.2 21.9 7.6 32.9 22.1 32.9 14.5 0 21.7-11.5 21.9-33.8 0-21.9-7.2-32.7-21.7-32.7-14.8 0-22.3 11.3-22.3 33.6z" class="st0"/>
+ <path id="XMLID_904_-5" d="M945.5 213c-9.3-.6-14-5.1-14-14v-57.8c-.6-11.7-7-17.4-19.6-17.4-12.6 0-18.9 5.7-19.1 17.4V213h-28.7v-66.1c0-32.1 16.2-48 48.2-48 32.1 0 47.8 15.9 47.6 48V213z" class="st0"/>
+ </g>
+ <g id="XMLID_899_-9" transform="matrix(.99987 0 0 .99987 .19 0)">
+ <path id="XMLID_902_-7" fill="#dadada" d="M153.7 244l36.5 38c1.2 1.2 3.1 2 5 2h89.5c5.2 0 8.3-4.7 5.2-8.1l-83-102.5c-1.8-2-5.2-1.9-6.9.1L153.4 238c-1.6 1.8-1.4 4.2.3 6z"/>
+ <linearGradient id="linearGradient936" x1="63.89" x2="63.89" y1="198.13" y2="-214.95" gradientUnits="userSpaceOnUse" xlink:href="#XMLID_15_-2">
+ <stop id="stop1003-5" offset="0" stop-color="#145643"/>
+ <stop id="stop1005-3" offset="1" stop-color="#14c441"/>
+ </linearGradient>
+ <path id="XMLID_901_-8" fill="#199053" d="M77 12.8C69.8 4.7 60.2 0 50.1 0 32.8 0 17.5 13.6 12.7 33.4 5.9 62 1.8 91.2.5 120.6l9.8 13.4c17.5 24.1 47.9 25.2 66.7 4.5 2.8-2.3 5.4-4.9 7.8-7.8l42.6-51.5c-.1 0-47.9-64.2-50.4-66.4z"/>
+ <path id="XMLID_900_-8" fill="#34cc5b" d="M284.6 0h-73.3c-12.9 0-24.8 5.9-31.7 15.7l-52.4 63.5-42.6 51.5c-2.4 2.9-5 5.4-7.8 7.8-18.8 20.7-49.2 19.6-66.7-4.5L.3 120.6c-1.4 30.8.2 61.7 4.9 92.2v.2c.4 2.3.7 4.7 1.1 7 .1.4.1.8.2 1.2a130.15 130.15 0 0 0 1.4 7.6l1.2 5.9.3 1.4c.5 2.2.9 4.4 1.4 6.6 0 .2.1.4.1.5 4.9 22.3 20.9 38.4 39.9 40.5.2 0 .3.1.5.1 1.3.1 2.5.2 3.8.2h.1c32.3 0 50.7-18.2 59.9-30.8L291.7 11.8c3.7-5.1-.3-11.8-7.1-11.8z"/>
+ <g aria-label="STATUS" style="line-height:125%;-inkscape-font-specification:'Montserrat, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start" id="text950" fill="#b3b3b3" stroke-width="3.12" font-family="Montserrat" font-size="118.63" font-weight="400" letter-spacing="0" word-spacing="0">
+ <path d="M1039.3 138.08q-6.37 0-10.5 2.62-4.12 2.62-4.12 8 0 5.25 4.12 8.12 4.13 2.75 17.5 6 13.49 3.24 20.24 9.12 6.87 5.87 6.87 17.36 0 11.37-8.62 18.5-8.62 7.12-22.62 7.12-20.49 0-36.36-14.12l9.25-11.12q13.24 11.5 27.49 11.5 7.12 0 11.24-3 4.25-3.13 4.25-8.13 0-5.12-4-7.87-3.87-2.87-13.5-5.12-9.61-2.37-14.61-4.25-5-2-8.87-5.12-7.75-5.87-7.75-18 0-12.11 8.75-18.6 8.87-6.63 21.86-6.63 8.37 0 16.62 2.75 8.25 2.75 14.24 7.74l-7.87 11.12q-3.87-3.5-10.5-5.74-6.61-2.25-13.11-2.25z" style="-inkscape-font-specification:'Montserrat, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start" id="path952"/>
+ <path d="M1119.42 140.08v73.84h-14.74v-73.84h-26.5v-13.5h67.73v13.5z" style="-inkscape-font-specification:'Montserrat, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start" id="path954"/>
+ <path d="M1160.15 194.05l-8.74 19.87h-15.75l38.48-87.34h15.75l38.48 87.34h-15.74l-8.75-19.87zm37.73-13.62l-15.86-35.98-15.87 35.98z" style="-inkscape-font-specification:'Montserrat, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start" id="path956"/>
+ <path d="M1258.58 140.08v73.84h-14.74v-73.84h-26.5v-13.5h67.73v13.5z" style="-inkscape-font-specification:'Montserrat, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start" id="path958"/>
+ <path d="M1315.3 193.93q5.99 7 16.24 7 10.24 0 16.24-7 6-7 6-19v-48.35h14.74v48.98q0 18.87-10.37 29.11-10.37 10.12-26.61 10.12-16.25 0-26.62-10.12-10.37-10.24-10.37-29.11v-48.98h14.75v48.36q0 11.99 6 18.99z" style="-inkscape-font-specification:'Montserrat, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start" id="path960"/>
+ <path d="M1407.73 138.08q-6.37 0-10.5 2.62-4.12 2.62-4.12 8 0 5.25 4.12 8.12 4.13 2.75 17.5 6 13.49 3.24 20.24 9.12 6.87 5.87 6.87 17.36 0 11.37-8.62 18.5-8.62 7.12-22.62 7.12-20.49 0-36.36-14.12l9.25-11.12q13.24 11.5 27.49 11.5 7.12 0 11.24-3 4.25-3.13 4.25-8.13 0-5.12-4-7.87-3.87-2.87-13.5-5.12-9.61-2.37-14.61-4.25-5-2-8.87-5.12-7.75-5.87-7.75-18 0-12.11 8.75-18.6 8.87-6.63 21.86-6.63 8.37 0 16.62 2.75 8.25 2.75 14.24 7.74l-7.87 11.12q-3.87-3.5-10.5-5.74-6.61-2.25-13.11-2.25z" style="-inkscape-font-specification:'Montserrat, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start" id="path962"/>
+ </g>
+ </g>
+</svg> \ No newline at end of file