diff options
Diffstat (limited to 'kamon-core/src/main/scala')
-rw-r--r-- | kamon-core/src/main/scala/kamon/context/BinaryPropagation.scala | 81 | ||||
-rw-r--r-- | kamon-core/src/main/scala/kamon/context/Context.scala | 126 | ||||
-rw-r--r-- | kamon-core/src/main/scala/kamon/context/HttpPropagation.scala | 65 | ||||
-rw-r--r-- | kamon-core/src/main/scala/kamon/instrumentation/HttpServer.scala | 24 | ||||
-rw-r--r-- | kamon-core/src/main/scala/kamon/metric/Accumulator.scala | 6 | ||||
-rw-r--r-- | kamon-core/src/main/scala/kamon/metric/Metric.scala | 24 | ||||
-rw-r--r-- | kamon-core/src/main/scala/kamon/metric/PeriodSnapshot.scala | 4 | ||||
-rw-r--r-- | kamon-core/src/main/scala/kamon/metric/Timer.scala | 6 | ||||
-rw-r--r-- | kamon-core/src/main/scala/kamon/package.scala | 2 | ||||
-rw-r--r-- | kamon-core/src/main/scala/kamon/tag/Lookups.scala | 32 | ||||
-rw-r--r-- | kamon-core/src/main/scala/kamon/tag/Tag.scala | 50 | ||||
-rw-r--r-- | kamon-core/src/main/scala/kamon/tag/TagSet.scala (renamed from kamon-core/src/main/scala/kamon/tag/Tags.scala) | 160 |
12 files changed, 384 insertions, 196 deletions
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 index 44ffb6f4..beb1996a 100644 --- a/kamon-core/src/main/scala/kamon/tag/Lookups.scala +++ b/kamon-core/src/main/scala/kamon/tag/Lookups.scala @@ -3,18 +3,27 @@ package kamon.tag import java.util.Optional import java.lang.{Boolean => JBoolean, Long => JLong, String => JString} -import kamon.tag.Tags.Lookup +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: Map[JString, Any]): 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 run(storage: Map[JString, Any]): JString = + override def execute(storage: Map[JString, Any]): JString = findAndTransform(key, storage, _plainString, null) } @@ -24,7 +33,7 @@ object Lookups { * 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 run(storage: Map[JString, Any]): Option[JString] = + override def execute(storage: Map[JString, Any]): Option[JString] = findAndTransform(key, storage, _stringOption, None) } @@ -34,7 +43,7 @@ object Lookups { * 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 run(storage: Map[String, Any]): Optional[String] = + override def execute(storage: Map[String, Any]): Optional[String] = findAndTransform(key, storage, _stringOptional, Optional.empty()) } @@ -47,7 +56,7 @@ object Lookups { * This lookup type is guaranteed to return a non-null String representation of value. */ def coerce(key: String) = new Lookup[String] { - override def run(storage: Map[String, Any]): String = { + override def execute(storage: Map[String, Any]): String = { val value = storage(key) if(value == null) "unknown" @@ -62,7 +71,7 @@ object Lookups { * associated with they is not a Boolean then a null is returned. */ def plainBoolean(key: String) = new Lookup[JBoolean] { - override def run(storage: Map[String, Any]): JBoolean = + override def execute(storage: Map[String, Any]): JBoolean = findAndTransform(key, storage, _plainBoolean, null) } @@ -72,7 +81,7 @@ object Lookups { * 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 run(storage: Map[String, Any]): Option[JBoolean] = + override def execute(storage: Map[String, Any]): Option[JBoolean] = findAndTransform(key, storage, _booleanOption, None) } @@ -82,7 +91,7 @@ object Lookups { * 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 run(storage: Map[String, Any]): Optional[JBoolean] = + override def execute(storage: Map[String, Any]): Optional[JBoolean] = findAndTransform(key, storage, _booleanOptional, Optional.empty()) } @@ -92,7 +101,7 @@ object Lookups { * associated with they is not a Long then a null is returned. */ def plainLong(key: String) = new Lookup[JLong] { - override def run(storage: Map[String, Any]): JLong = + override def execute(storage: Map[String, Any]): JLong = findAndTransform(key, storage, _plainLong, null) } @@ -102,7 +111,7 @@ object Lookups { * 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 run(storage: Map[String, Any]): Option[JLong] = + override def execute(storage: Map[String, Any]): Option[JLong] = findAndTransform(key, storage, _longOption, None) } @@ -112,7 +121,7 @@ object Lookups { * 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 run(storage: Map[String, Any]): Optional[JLong] = + override def execute(storage: Map[String, Any]): Optional[JLong] = findAndTransform(key, storage, _longOptional, Optional.empty()) } @@ -134,6 +143,7 @@ object Lookups { 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) 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/Tags.scala b/kamon-core/src/main/scala/kamon/tag/TagSet.scala index b7813da6..1090ca5a 100644 --- a/kamon-core/src/main/scala/kamon/tag/Tags.scala +++ b/kamon-core/src/main/scala/kamon/tag/TagSet.scala @@ -1,47 +1,12 @@ package kamon.tag -import kamon.tag.Tags.Lookup +import kamon.tag.TagSet.Lookup import scala.collection.JavaConverters.asScalaIteratorConverter import java.lang.{Boolean => JBoolean, Long => JLong, String => JString} import org.slf4j.LoggerFactory - -/** - * Marker trait for allowed Tag implementations. Users are not meant to create implementations of this trait outside - * of Kamon. - */ -sealed trait Tag - -object Tag { - - /** - * Represents a String key pointing to a String value. - */ - trait String extends Tag { - def key: JString - def value: JString - } - - /** - * Represents a String key pointing to a Boolean value. - */ - trait Boolean extends Tag { - def key: JString - def value: JBoolean - } - - /** - * Represents a String key pointing to a Long value. - */ - trait Long extends Tag { - def key: JString - def value: JLong - } -} - - /** * A immutable collection of key/value pairs with specialized support for storing String keys pointing to String, Long * and/or Boolean values. @@ -51,8 +16,8 @@ object Tag { * 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. * - * Tags can only be created from the builder functions on the Tags companion object. There are two different options - * to read the contained pairs from a Tags instance: + * 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 @@ -69,80 +34,96 @@ object Tag { * cumbersome operation is rarely necessary on user-facing code. * */ -class Tags private(private val _tags: Map[String, Any]) { - import Tags.withPair +class TagSet private(private val _tags: Map[String, Any]) { + import TagSet.withPair /** - * Creates a new Tags instance that includes the provided key/value pair. If the provided key was already associated + * 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): Tags = + def withTag(key: String, value: JString): TagSet = withPair(this, key, value) /** - * Creates a new Tags instance that includes the provided key/value pair. If the provided key was already associated + * 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): Tags = + def withTag(key: String, value: JBoolean): TagSet = withPair(this, key, value) /** - * Creates a new Tags instance that includes the provided key/value pair. If the provided key was already associated + * 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): Tags = + def withTag(key: String, value: JLong): TagSet = withPair(this, key, value) /** - * Creates a new Tags instance that includes all the tags from the provided Tags instance. If any of the tags in this + * 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: Tags): Tags = - new Tags(_tags ++ other._tags) + def withTags(other: TagSet): TagSet = + new TagSet(_tags ++ other._tags) /** - * Creates a new Tags instance that includes the provided key/value pair. If the provided key was already associated + * 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): Tags = + def and(key: String, value: JString): TagSet = withPair(this, key, value) /** - * Creates a new Tags instance that includes the provided key/value pair. If the provided key was already associated + * 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): Tags = + def and(key: String, value: JBoolean): TagSet = withPair(this, key, value) /** - * Creates a new Tags instance that includes the provided key/value pair. If the provided key was already associated + * 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): Tags = + def and(key: String, value: JLong): TagSet = withPair(this, key, value) /** - * Creates a new Tags instance that includes all the tags from the provided Tags instance. If any of the tags in this + * 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: Tags): Tags = - new Tags(_tags ++ other._tags) + def and(other: TagSet): TagSet = + new TagSet(_tags ++ other._tags) + /** - * Executes a tag lookup on the instance. The return type of this function will depend on the provided Lookup - * instance. Take a look at the built-in lookups on the Tags.Lookup companion object for more information. + * Returns whether this TagSet instance does not contain any tags. + */ + def isEmpty(): Boolean = + _tags.isEmpty + + + /** + * Returns whether this TagSet instance contains any tags. + */ + def nonEmpty(): Boolean = + _tags.nonEmpty + + + /** + * 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.run(_tags) + lookup.execute(_tags) + /** * Returns a immutable sequence of tags created from the contained tags internal representation. Calling this method @@ -160,6 +141,7 @@ class Tags private(private val _tags: Map[String, Any]) { } } + /** * 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 @@ -205,7 +187,7 @@ class Tags private(private val _tags: Map[String, Any]) { override def equals(other: Any): Boolean = - other != null && other.isInstanceOf[Tags] && other.asInstanceOf[Tags]._tags == this._tags + other != null && other.isInstanceOf[TagSet] && other.asInstanceOf[TagSet]._tags == this._tags override def toString: JString = { @@ -228,15 +210,15 @@ class Tags private(private val _tags: Map[String, Any]) { } private object immutable { - class String(val key: JString, val value: JString) extends Tag.String - class Boolean(val key: JString, val value: JBoolean) extends Tag.Boolean - class Long(val key: JString, val value: JLong) extends Tag.Long + 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 { - class String(var key: JString, var value: JString) extends Tag.String with Updateable[JString] - class Boolean(var key: JString, var value: JBoolean) extends Tag.Boolean with Updateable[JBoolean] - class Long(var key: JString, var value: JLong) extends Tag.Long with Updateable[JLong] + 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 @@ -251,63 +233,63 @@ class Tags private(private val _tags: Map[String, Any]) { } } -object Tags { +object TagSet { /** * A valid instance of tags that doesn't contain any pairs. */ - val Empty = new Tags(Map.empty.withDefaultValue(null)) + val Empty = new TagSet(Map.empty.withDefaultValue(null)) /** - * Construct a new Tags instance with a single key/value pair. + * Construct a new TagSet instance with a single key/value pair. */ - def from(key: String, value: JString): Tags = + def from(key: String, value: JString): TagSet = withPair(Empty, key, value) /** - * Construct a new Tags instance with a single key/value pair. + * Construct a new TagSet instance with a single key/value pair. */ - def from(key: String, value: JBoolean): Tags = + def from(key: String, value: JBoolean): TagSet = withPair(Empty, key, value) /** - * Construct a new Tags instance with a single key/value pair. + * Construct a new TagSet instance with a single key/value pair. */ - def from(key: String, value: JLong): Tags = + def from(key: String, value: JLong): TagSet = withPair(Empty, key, value) /** - * Constructs a new Tags instance from a Map. The returned Tags 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. + * 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]): Tags = - new Tags(map.filter { case (k, v) => isValidPair(k, v) } withDefaultValue(null)) + def from(map: Map[String, Any]): TagSet = + new TagSet(map.filter { case (k, v) => isValidPair(k, v) } withDefaultValue(null)) /** - * Constructs a new Tags instance from a Map. The returned Tags 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. + * 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]): Tags = { + def from(map: java.util.Map[String, Any]): TagSet = { val allowedTags = Map.newBuilder[String, Any] map.entrySet() .iterator() .asScala .foreach(e => if(isValidPair(e.getKey, e.getValue)) allowedTags += (e.getKey -> e.getValue)) - new Tags(allowedTags.result().withDefaultValue(null)) + new TagSet(allowedTags.result().withDefaultValue(null)) } - private val _logger = LoggerFactory.getLogger(classOf[Tags]) + private val _logger = LoggerFactory.getLogger(classOf[TagSet]) - private def withPair(parent: Tags, key: String, value: Any): Tags = + private def withPair(parent: TagSet, key: String, value: Any): TagSet = if(isValidPair(key, value)) - new Tags(parent._tags.updated(key, value)) + new TagSet(parent._tags.updated(key, value)) else parent @@ -333,7 +315,7 @@ object Tags { /** - * Describes a strategy to lookup values from a Tags instance. Implementations of this interface will be provided + * 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. * @@ -341,6 +323,6 @@ object Tags { * definitions when looking up keys from a Tags instance. */ trait Lookup[T] { - def run(storage: Map[String, Any]): T + def execute(storage: Map[String, Any]): T } }
\ No newline at end of file |